2009-04-09 6 views
3

Existe-t-il un moyen de définir une macro (ou quelque chose de similaire) qui permettrait aux objets d'être alloués sur la pile ou sur le tas, proprement?Modèle C/C++ à USE_HEAP ou USE_STACK

par ex. Code actuel:

A a; 
a.someFunc(); 

La suggestion la plus simple pourrait être la suivante, mais comme vous pouvez le voir ci-dessous, il est pas très propre pour maintenir 2 ensembles de code.

#ifdef USE_STACK 
    A a; 
    a.someFunc(); 
#elseif USE_HEAP 
    A* a = new A(); 
    a->someFunc(); 
#endif 

Je cherche un modèle de conception/classe proxy qui peut être utilisé pour compiler le code ou l'autre manière, en fonction des besoins de nos clients.

Éditer: Le code est utilisé pour construire une bibliothèque pour périphérique embarqué/(intégré) Linux/Windows Mobile. La plupart des clients souhaitent uniquement une allocation par pile. Quelques autres ont demandé d'échanger la pile contre le tas.

Merci, Charles

Répondre

5

EDIT: amélioré pour permettre d'appeler des fonctions membres enveloppées par operator->

expansion sur la réponse de Manuel pour le rendre plus complet , essayez ceci:

#include <iostream> 

#define USE_STACK 

template <class T> 
class HeapWrapper { 
#ifdef USE_STACK 
    T obj_; 
#else 
    T *obj_; 
#endif 
public: 
#ifdef USE_STACK 
    HeapWrapper() : obj_() {} 

    template <class A1> 
    HeapWrapper(const A1 &a1) : obj_(a1) {} 

    template <class A1, class A2> 
    HeapWrapper(const A1 &a1, const A2 &a2) : obj_(a1, a2) {} 

    // etc 

#else 
    HeapWrapper() : obj_(new T()) {} 
    ~HeapWrapper() { delete obj_; } 

    template <class A1> 
    HeapWrapper(const A1 &a1) : obj_(new T(a1)) {} 

    template <class A1, class A2> 
    HeapWrapper(const A1 &a1, const A2 &a2) : obj_(new T(a1, a2)) {} 

    // etc 
#endif 

#ifdef USE_STACK 
    operator const T &() const { return obj_; } 
    operator T &()    { return obj_; } 
    T *operator->()    { return &obj_; } 
    T& operator*()    { return obj_; } 
#else 
    operator const T &() const { return *obj_; } 
    operator T &()    { return *obj_; } 
    T *operator->()    { return obj_; } 
    T& operator*()    { return *obj_; } 
#endif 

    // cast operators makes this work nicely 
    HeapWrapper &operator=(const T &rhs) { *obj_ = rhs; return *this;} 
}; 


class A { 
public: 
    void member(int x) { 
     std::cout << x << std::endl; 
    } 
}; 


int main() { 
    HeapWrapper<int> x1(5); 
    HeapWrapper<int> x2; 
    HeapWrapper<int> x3 = x1; 
    HeapWrapper<int> x4 = 3; 

    std::cout << x1 << " " << x2 << " " << x3 << " " << x4 << std::endl; 

    // example using a custom class's members.. 
    HeapWrapper<A> a1; 
    a1->member(5); 
} 
+0

Cela semble vraiment bien, Evan. Existe-t-il un moyen d'encapsuler ceci dans la classe A? Il serait plus facile de changer une classe que de mettre à jour tout le code client comme indiqué ci-dessus. – Charles

+0

Eh bien, pour ce faire, vous utilisez un modèle d'usine. Fondamentalement, vous ajoutez une fonction statique à A qui renvoie HeapWrapper ou quelque chose comme ça. Malheureusement, il faudrait encore changer tout le code qui crée l'objet de A. Pas vraiment une énorme différence dans le nombre de changements que vous auriez à faire. –

+0

Aussi, pourquoi quelqu'un at-il déprécié cela? C'est une amélioration par rapport à la réponse actuellement votée la plus haute ... –

1

Habituellement, la pile est limitée en taille, de sorte que les gros objets doivent être attribués de toute façon tas.

Toutes les instances qui mettent en œuvre un idiome de garde RAII (par exemple pour Aquire et la libération d'un mutex) doivent être placés sur la pile, de sorte qu'ils sont nettoyées si vous laissez le contexte (par exemple par retour ou exception)

Un idiome qui vous laisse basculer n'est généralement pas très utile, car une décision prudente doit être prise en tant que temps pour placer des objets sur la pile ou sur le tas. Habituellement, l'un ou l'autre traitera mieux le problème, et il ne serait alors plus logique de changer.

+0

ils ne le font pas, vous devez utiliser un pointeur intelligent pour obtenir la même technique. le pointeur intelligent est basé sur une pile, mais la mémoire qu'il contient utilise le tas. – gbjbaanb

2

Quelque chose comme cela pourrait aider:

template <typename T> 
class HeapWrapper 
{ 
#ifdef USE_STACK 
    T obj_; 
#else 
    T *obj_; 
#endif 
public: 
#ifdef USE_STACK 
    HeapWrapper() : obj_() {} 
#else 
    HeapWrapper() : obj_(new T()) {} 
#endif 

#ifdef USE_STACK 
    const T& obj() const 
    { return obj_; } 

    T& obj() const 
    { return obj_; } 
#else 
    const T& obj() const 
    { return *obj_; } 

    T& obj() const 
    { return *obj_; } 
#endif 
}; 

Cependant, notez que ce soit vous limite à des objets avec le constructeur par défaut uniquement. La classe enveloppé pourrait fournir une fonction Init(...) qui pourrait être transmis par un variadic template function par la classe wrapper (ou simplement ajouter un template <typename T1, template T2, [etc]> Init(const T1 &x1, cons tT2 &x2) pour chaque arité vous avez besoin):

template <typename T1> 
void Init(const T1& x1) 
{ 
#ifdef USE_STACK 
    obj_.Init(x1); 
#else 
    obj_->Init(x1); 
#endif 
} 

template <typename T1, typename T2> 
void Init(const T1& x1, const T2& x2) 
{ 
#ifdef USE_STACK 
    obj_.Init(x1, x2); 
#else 
    obj_->Init(x1, x2); 
#endif 
} 

points bonus si votre compilateur a variadic templates déjà:

template<typename... T> 
void foo(const T&... values) { 
#ifdef USE_STACK 
    obj_.Init(values...); 
#else 
    obj_->Init(values...); 
#endif 
} 
+0

pourquoi ne pas faire la chose du modèle variadique avec le constructeur réel? –

+0

Salut Manuel, bonne idée, pourriez-vous utiliser votre exemple pour montrer comment utiliser HeapWrapper avec l'objet A ci-dessus et appeler a.someFunc()? – Charles

+0

@Evan: Je n'étais pas sûr que cela fonctionnerait. Bon appel! – Manuel

2

Si vous posez cette question, je pense que vous devriez commencer à chercher un allocateur personnalisé. Pour commencer, si vous allouez un gros bloc de mémoire, vous pouvez l'utiliser comme une pile ou un tas en changeant les internes de l'allocateur. Vous alloueriez tous les objets à l'aide d'un pointeur intelligent (pour éviter les fuites de mémoire) et toute la mémoire vous serait distribuée depuis votre tas personnalisé. Ceci vous permet d'ajuster l'implémentation de l'allocation en fonction des besoins de votre client, tout en vous donnant les performances d'un allocateur basé sur la pile si vous le faites de cette manière.

Votre code utiliserait toujours la création d'objets de style "heap" de sorte que vous n'auriez besoin que d'un mode de codage et que vous n'auriez pas besoin des macros conditionnelles. Vérifiez les allocateurs de blocs (qui créent plusieurs tas de blocs de taille fixe, vous donnez le premier bloc libre de la taille suivante jusqu'à l'appelant (par exemple, les blocs sont de 16 octets, 32 octets, 64 octets, etc.), allouer et libérer, bien qu'il soit inefficace avec l'utilisation de la mémoire).

0

Pas une réponse définitive, mais je l'espère peut aider:

#ifdef USE_STACK 
    A a; 
#elseif USE_HEAP 
    auto_ptr<A> CAUTION_USE_OF_AUTOPTR_a(new A()); 
    A& a = CAUTION_USE_OF_AUTOPTR_a.get(); 
#endif 
a.someFunc(); 
Questions connexes