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
}
};
3 réponses
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.
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);
}
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]);
De nouvelles questions
c++
C ++ est un langage de programmation à usage général. Il a été conçu à l'origine comme une extension de C et a une syntaxe similaire, mais c'est maintenant un langage complètement différent. Utilisez cette balise pour les questions sur le code (à être) compilé avec un compilateur C ++. Utilisez une balise spécifique à la version pour les questions liées à une révision standard spécifique [C ++ 11], [C ++ 14], [C ++ 17], [C ++ 20] ou [C ++ 23], etc. .