Pour écrire une classe de liste de modèles, l'équivalent de vector (purement comme exercice de conception), j'essaie de savoir ce qui est fait pour être efficace.

Si l'on écrit:

v = new T[size];

Alors le compilateur appellera le constructeur pour T, non? T().

Donc, à la place dans la classe ci-dessous:

v = (T*) new char[sizeof(T) * size]

Cela semble assez facile. Cependant, dans le destructeur, comment supprimer uniquement ceux qui ont été initialisés? Dans la classe suivante, seuls les premiers éléments "utilisés" sont initialisés.

De plus, dans le constructeur de copie, comment appeler efficacement le constructeur de copie pour T uniquement pour les éléments utilisés?

Si j'ai initialisé de manière coûteuse, cela fonctionne:

v = new T[size];
for (int i = 0; i < used; i++)
  v[i] = orig.v[i];

Mais cela nécessite que v soit déjà initialisé avec T(). Quelle est la meilleure façon?

La classe est ci-dessous:

#include <cstdint>

template<typename T>
class List {
private:
    uint32_t used;
    uint32_t capacity;
    T* v;
public:
    List(uint32_t cap) : used(0), capacity(cap), v((T*)new char[sizeof(T)*capacity]) {}
    ~List() {
        delete [] v; // no
    }
    List(const List& orig) : used(orig.used), capacity(orig.capacity), v((T*) new char[sizeof(T)*capacity]) {
        // now copy only the used ones
        for (int i = 0; i < used; i++)
            v[i] = orig.v[i]; // no, operator = will call destructor on v[i], but it is uninitialized
    }
};
0
Dov 20 avril 2017 à 03:28

3 réponses

Meilleure réponse

Pour ressembler à std::vector, vous devez utiliser "placement nouveau" et appeler explicitement des destructeurs.

#include <new>

~List() {
    while (used) {
        --used;
        v[used]->~T();
    }
    delete[] reinterpret_cast<char*>(v);
}

List(const List& orig) : used(orig.used), capacity(orig.capacity),
    v(reinterpret_cast<T*>(new char[sizeof(T)*capacity])) {
    // now copy only the used ones
    for (int i = 0; i < used; i++)
        new(v+i) T(orig.v[i]);
}

Notez que le constructeur de copie ci-dessus n'est pas protégé contre les exceptions. Essayez de le faire.

1
aschepler 20 avril 2017 à 00:48

Pour dans le constructeur de copie, vous pouvez essayer ce code:

#include <cstring>

List(const List& orig) : used(orig.used), capacity(orig.capacity), v((T*) new char[sizeof(T) * capacity]) {
        // now copy only the used ones
        memcpy(v, orig.v, sizeof(T)*capacity);
    }

Ou

List(const List& orig) : used(orig.used), capacity(orig.capacity), v((T*) new char[sizeof(T) * capacity]) {
        // now copy only the used ones
        memcpy_s(v, capacity, orig.v, sizeof(T)*capacity);
    }
0
Ananta Yudica 20 avril 2017 à 01:27

Tout d'abord, utilisez simplement std::vector<T> au lieu de le réimplémenter vous-même.

Vous recherchez ici un placement nouveau et des appels de destructeurs explicites . Là où normalement, chaque new doit être associé à un delete, chaque nouvel emplacement doit être associé à un appel de destructeur explicite.

Pour répondre à vos questions spécifiques:

Cependant, dans le destructeur, comment supprimer uniquement ceux qui ont été initialisés?

Appelez explicitement leurs destructeurs, puis delete[] l'allocation char[] d'origine correctement, qui n'appellera (correctement) pas automatiquement les destructeurs T.

for (uint32_t i = 0; i < used; ++i) {
    v[i]->~T();
}

delete [] reinterpret_cast<char *>(v);

De plus, dans le constructeur de copie, comment appeler efficacement le constructeur de copie pour T uniquement pour les éléments utilisés?

Vous devez placer un nouveau ici. Votre ligne v[i] = orig.v[i]; provoque un comportement indéfini car v[i] n'a pas encore été construit.

Placement-new les objets à la place (ce que vous devriez faire à chaque v[i] avant de l'utiliser):

new(reinterpret_cast<char *>(v + i)) T(orig.v[i]);
0
cdhowie 20 avril 2017 à 00:46