2010-04-18 9 views
5

Comment supprimer l'initialisation automatique et la destruction d'un type? Alors qu'il est merveilleux que T buffer[100] initialise automatiquement tous les éléments de buffer, et les détruit quand ils tombent hors de portée, ce n'est pas le comportement que je veux.C++ Supprimer l'initialisation automatique et la destruction

#include <iostream> 

static int created = 0, 
      destroyed = 0; 

struct S 
{ 
    S() 
    { 
     ++created; 
    } 
    ~S() 
    { 
     ++destroyed; 
    } 
}; 

template <typename T, size_t KCount> 
class fixed_vector 
{ 
private: 
    T m_buffer[KCount]; 
public: 
    fixed_vector() 
    { 
     // some way to suppress the automatic initialization of m_buffer 
    } 

    ~fixed_vector() 
    { 
     // some way to suppress the automatic destruction of m_buffer 
    } 
}; 

int main() 
{ 
    { 
     fixed_vector<S, 100> arr; 
    } 

    std::cout << "Created:\t" << created << std::endl; 
    std::cout << "Destroyed:\t" << destroyed << std::endl; 
    return 0; 
} 

La sortie de ce programme est:

Created: 100 
Destroyed: 100 

Je voudrais que ce soit:

Created: 0 
Destroyed: 0 

Ma seule idée est de faire m_buffer un certain type trivialement construit et destructed comme char et puis compter sur operator[] pour envelopper le calculateur de pointeur pour moi, bien que cela ressemble à une solution horriblement piraté. Une autre solution serait d'utiliser malloc et free, mais cela donne un niveau d'indirection que je ne veux pas.


La raison pour laquelle je veux que c'est parce que je fais un récipient et je ne veux pas payer pour les frais généraux d'initialisation des choses que je ne vais pas utiliser. Par exemple, si ma fonction main était:

int main() 
{ 
    { 
     std::vector<S> vec; 
     vec.reserve(50); 
    } 

    std::cout << "Created:\t" << created << std::endl; 
    std::cout << "Destroyed:\t" << destroyed << std::endl; 
    return 0; 
} 

La sortie serait correct:

Created: 0 
Destroyed: 0 
+0

Pourquoi diable voulez-vous un tableau d'instances 'S' non construites? Que pouvez-vous utiliser pour? –

+0

Rien - quel est le point. J'ai révisé ma question avec justification. –

+0

Donc, si je vous comprends bien, vous voulez réserver de l'espace dans votre vecteur pour les éléments que vous remplirez plus tard, et vous ne voulez pas payer pour la construction (et la destruction) des éléments deux fois. Cette différence de performance est-elle vraiment importante? Avez-vous profilé votre application et vérifié que c'est là que vous passez le plus de temps lors de l'exécution? –

Répondre

3

Vous pouvez créer la matrice sous la forme char s, puis utiliser placement new pour créer les éléments en cas de besoin.

template <typename T, size_t KCount> 
class Array 
{ 
private: 
    char m_buffer[KCount*sizeof(T)]; // TODO make sure it's aligned correctly 

    T operator[](int i) { 
     return reinterpret_cast<T&>(m_buffer[i*sizeof(T)]); 
    } 

Après re-lecture de votre question, il semble que vous voulez un tableau clairsemé, cela va parfois sous le nom de carte; o) (bien sûr les caractéristiques de performance sont différentes ...)

template <typename T, size_t KCount> 
class SparseArray { 
    std::map<size_t, T> m_map; 
public: 
    T& operator[](size_t i) { 
     if (i > KCount) 
      throw "out of bounds"; 
     return m_map[i]; 
    } 
+1

Je pensais 'reinterpret_cast', puisque la sémantique a plus de sens pour moi. Mais j'espérais vraiment éviter cela - est-il possible de ne pas avoir à faire cela? –

+0

Vous avez raison, 'static_cast' est le mauvais choix, mettant à jour ma réponse. – Motti

+0

Votre première solution est ce que je cherche et c'est exactement ce que je veux. –

0

Vous pouvez jeter un oeil à la façon dont il est fait avec les conteneurs STL, mais je doute que vous pouvez épargner vous malloc s et free de

+0

Je peux m'épargner 'malloc' et' free' en utilisant un 'char m_buffer [KCount * sizeof (T)]' (bien que évidemment avec une sémantique différente, puisque cela n'est pas autorisé). –

1

Ce code:

#include <iostream> 
#include <vector> 
using namespace std; 

int created = 0, destroyed = 0; 

struct S 
{ 
    S() 
    { 
     ++created; 
    } 
    S(const S & s) { 
     ++created; 
    } 
    ~S() 
    { 
     ++destroyed; 
    } 
}; 

int main() 
{ 
    { 
     std::vector<S> vec; 
     vec.reserve(50); 
    } 

    std::cout << "Created:\t" << created << std::endl; 
    std::cout << "Destroyed:\t" << destroyed << std::endl; 
    return 0; 
} 

a exactement la sortie que vous voulez - je ne suis pas sûr de savoir quelle est votre question.

+0

il ne peut pas faire 'vec [1]' après. (construction paresseuse) –

+0

C'est correct (comme indiqué dans ma question), mais comment 'std :: vector' fait-il cela? En regardant à travers l'implémentation de g ++, il semble * qu'ils * font un certain nombre de 'reinterpret_cast's sur un' char [] 'pour éviter l'initialisation jusqu'à la dernière seconde. –

+0

Le vecteur @Travis crée une mémoire non initialisée, puis utilise le placement new pour créer les objets réels dans celui-ci selon les besoins. –

4

Vous pouvez regarder dans boost::optional

template <typename> struct tovoid { typedef void type; }; 

template <typename T, size_t KCount, typename = void> 
struct ArrayStorage { 
    typedef T type; 
    static T &get(T &t) { return t; } 
}; 

template <typename T, size_t KCount> 
struct ArrayStorage<T, KCount, typename tovoid<int T::*>::type> { 
    typedef boost::optional<T> type; 
    static T &get(boost::optional<T> &t) { 
    if(!t) t = boost::in_place(); 
    return *t; 
    } 
}; 

template <typename T, size_t KCount> 
class Array 
{ 
public: 
    T &operator[](std::ptrdiff_t i) { 
     return ArrayStorage<T, KCount>::get(m_buffer_[i]); 
    } 

    T const &operator[](std::ptrdiff_t i) const { 
     return ArrayStorage<T, KCount>::get(m_buffer_[i]); 
    } 

    mutable typename ArrayStorage<T, KCount>::type m_buffer_[KCount]; 
}; 

Une spécialisation est effectuée pour le type de classe qui les enveloppe dans un optional, appelant ainsi le constructeur/destructeur paresseusement. Pour les types hors classe, nous n'avons pas besoin de ce wrapping. Ne pas les encapsuler signifie que nous pouvons traiter &a[0] comme une zone mémoire contiguë et transmettre cette adresse aux fonctions C qui veulent un tableau. boost::in_place créera les types de classe sur place, sans utiliser un T temporaire ou son constructeur de copie.

Ne pas utiliser l'héritage ou les membres privés permettent la classe de rester un agrégat, ce qui permet une forme pratique d'initialisation

// only two strings are constructed 
Array<std::string, 10> array = { a, b }; 
+0

C'était en fait la première chose que j'ai regardée. Cependant, un 'boost: optionnel ' a un drapeau supplémentaire pour lui donner la possibilité d'utiliser le 'operator bool()', donc un 'lazy_array ' prendrait inutilement le double de l'espace dont il a besoin. –

+0

@Travis soit vous allez pour la vitesse, ou vous allez pour l'espace. Je ne pense pas que cela puisse être résolu sans un drapeau. Comment allez-vous voir si 'm_buffer [i]' n'est pas encore construit? L'élément placé dans cette fente peut établir n'importe quelle combinaison possible de bits, ainsi vous ne pouvez pas déterminer cet état par des moyens purs de cette cellule de mémoire. Mais vraiment, c'est juste un bool. Si vous allez pour l'espace, vous pouvez allouer un tableau de 'T *' et l'initialiser à des pointeurs NULL, puis utiliser 'new' pour allouer vos éléments. Cependant, je ne suis même pas sûr que cela va utiliser moins d'espace dans les coulisses pour la gestion des tas. –

+0

Je suppose que je pourrais avoir spécifié dans ma question initiale, mais dans ce cas, je reçois le meilleur des deux mondes, car il s'agit d'un conteneur. Sur 'push_back', je peux traiter l'élément comme non initialisé et' pop_back' je peux traiter l'élément comme initialisé. Donc, tout '[0, size())' est initialisé et '[size(), KCount)' est initialisé. –

1

Si vous voulez être comme vecteur, vous devez faire quelque chose comme ceci:

template <typename T> 
class my_vector 
{ 
    T* ptr; // this is your "array" 
    // ... 
    public: 

    void reserve(size_t n) 
    { 
     // allocate memory without initializing, you could as well use malloc() here 
     ptr = ::operator new (n*sizeof(T)); 
    } 

    ~my_vector() 
    { 
     ::operator delete(ptr); // and this is how you free your memory 
    } 

    void set_element(size_t at, const T& element = T()) 
    { 
     // initializes single element 
     new (&ptr[at]) T(element); // placement new, copies the passed element at the specified position in the array 
    } 

    void destroy_element(size_t at) 
    { 
     ptr[at].~T(); // explicitly call destructor 
    } 
}; 

Ce code est évidemment uniquement pour la démonstration, j'ai omis le constructeur de copie de my_vector et tout suivi sur ce qui est créé et non (appeler un destructeur sur un emplacement que vous n'avez pas appelé constructeur est probablement un comportement indéfini). De plus, les allocations et désallocations vector de STL sont supprimées par l'utilisation d'allocateurs (le deuxième argument de modèle vector).

Espérons que ça aide

Questions connexes