Reputation: 8542
For writing a template list class, the equivalent of vector (purely as a design exercise) I am trying to find out what is done to be efficient.
If one writes:
v = new T[size];
then the compiler will call the constructor for T
, right? T()
.
So instead in the class below:
v = (T*) new char[sizeof(T) * size]
This seems easy enough. However, in the destructor, how to delete just the ones that have been initialized? In the following class, only the first "used" elements are initialized.
Also, in the copy constructor, how to call the copy constructor for T
for only the used elements efficiently?
If I initialized the expensive way, it works:
v = new T[size];
for (int i = 0; i < used; i++)
v[i] = orig.v[i];
but that requires that v
already be intiialized with T()
. What is the better way?
Class is below:
#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
}
};
Upvotes: 0
Views: 48
Reputation: 11
For in the copy constructor you can try this 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);
}
or
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);
}
Upvotes: 0
Reputation: 72271
To be like std::vector
, you need to use "placement new" and explicitly call destructors.
#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]);
}
Note the above copy constructor is not exception-safe. Try to make it so.
Upvotes: 1
Reputation: 168988
First, just use std::vector<T>
instead of reimplementing this yourself.
What you're looking for here is placement new and explicit destructor calls. Where normally, each new
should be paired with a delete
, each placement new should be paired with an explicit destructor call.
To answer your specific questions:
However, in the destructor, how to delete just the ones that have been initialized?
Explicitly call their destructors, then delete[]
the original char[]
allocation correctly, which will (correctly) not automatically call any T
destructors.
for (uint32_t i = 0; i < used; ++i) {
v[i]->~T();
}
delete [] reinterpret_cast<char *>(v);
Also, in the copy constructor, how to call the copy constructor for
T
for only the used elements efficiently?
You need to placement-new here. Your line v[i] = orig.v[i];
causes undefined behavior because v[i]
has not yet been constructed.
Placement-new the objects instead (which you should do to each v[i]
before you use it):
new(reinterpret_cast<char *>(v + i)) T(orig.v[i]);
Upvotes: 0