Implementing efficient initialization of C++ Template List classes - c++

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
}
};

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.

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]);

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);
}

Related

how to work with deleted object on custom vector

I know that std vectors can work with objects that are not default constructible. However, when I try to implement a slightly modified one myself, I cant seem to make such vector.
class A
{
public:
A() = delete;
A(const int &x)
:x(x)
{}
private:
int x;
};
template <typename T, int N> //default constructor of the vector
CircularBuffer<T,N>::CircularBuffer()
{
Size = 0;
Capacity =N;
Array = new T[Capacity];
Start = 0;
End = 0;
}
template <typename T, int N>
CircularBuffer<T,N>::CircularBuffer(const CircularBuffer& rhs) //copy constructor of the vector
{
Size = rhs.Size;
Capacity = rhs.Capacity;
Array = new T[Capacity];
Start = rhs.Start;
End = rhs.End;
for(int i=0;i<Capacity;i++)
{
this->Array[i] = rhs.Array[i];
}
}
template <typename T, int N> //move constructor of the vector
CircularBuffer<T,N>::CircularBuffer(CircularBuffer&& rhs)
{
rhs.Swap(*this);
}
template <typename T, int N>
void CircularBuffer<T,N>:: Swap(CircularBuffer &source)
{
swap(Size,source.Size);
swap(Capacity,source.Capacity);
swap(Start,source.Start);
swap(End,source.End);
swap(Array,source.Array);
}
When I try to make a vector with object A,
CircularBuffer<A,3> v;
error: use of deleted function ‘A::A()’
I get this error which is obviously self explanatory. Anyone can help me solve this??
The problem is here
Array = new T[Capacity];
which default constructs T objects.
std::vector uses placement new to construct objects when they are added to the vector.
Array = (T*)operator new(sizeof(T)*Capacity);
and (when you add the new item)
new(Array + i) T(...); // placement new
where ... are the arguments you wish to pass to the constructor.
See here for more details.
The issue is with the line Array = new T[Capacity];. This needs to default-initialize all members of the array, which it cannot do without a default-constructor.
std::vector gets around it by splitting allocation and construction with its allocator. I recommend you simply use std::allocator as well. Otherwise you can use placement-new to construct the elements after allocating the required memory separately.

C++ rule of 5 - segmentation fault on custom destructor

I have been learning C++ for some time and started writing a larger project just to realise that whatever I was thinking about C++ is wrong.
Basically I have a class called DenseVector which holds doubles. I want to move, copy construct etc that vector to use it with vectors etc.
The class looks something like this:
DenseVector.h:
class DenseVector {
private:
double* values = nullptr;
int size;
public:
DenseVector();
DenseVector(int size);
DenseVector(double x, double y);
DenseVector(double x, double y, double z);
DenseVector(const DenseVector &other);
DenseVector(DenseVector &&other);
virtual ~DenseVector();
DenseVector& operator=(const DenseVector& other);
DenseVector& operator=(DenseVector&& other);
}
DenseVector.cpp
DenseVector::DenseVector() : DenseVector(3) {}
DenseVector::DenseVector(int size) : size(size) {
this->values = new double[size + size % 2]{0};
}
DenseVector::DenseVector(double x, double y) {
this->size = 2;
this->values = new double[2]{x,y};
}
DenseVector::DenseVector(double x, double y, double z) {
this->size = 3;
this->values = new double[4]{x,y,z};
}
DenseVector::DenseVector(const DenseVector &other) : size(other.size) {
this->values = new double[size + size % 2]{0};
memcpy(values, other.values, size * sizeof(double));
}
DenseVector::DenseVector(DenseVector &&other) : values(other.values), size(other.size){}
DenseVector::~DenseVector() {
if(values != nullptr){
delete values;
values = nullptr;
}
}
DenseVector &DenseVector::operator=(const DenseVector &other) {
this->size = other.size;
memcpy(values, other.values, size * sizeof(double));
return *this;
}
DenseVector &DenseVector::operator=(DenseVector &&other) {
this->values = other.values;
this->size = other.size;
return *this;
}
I assume its a very straight forward implementation for mathematical vectors in C++. Note that size of the array internally is always a multiple of two. This is due to speedups using AVX/SSE which is not part of this question.
Basically I keep getting a Segmentation fault inside the deconstructor when trying to delete the value and I have no idea why this keeps on happening!
And example would be the following snippet:
std::vector<DenseVector> positions;
for(int i = 0; i < 10; i++){
positions.push_back({1,2,3});
}
This really confuses me and I would be very very happy if someone could help me with this problem as this had happened to me before many times in other programs.
Also what would be the difference between using push_back and emplace_back in this case? Should one prefer one over the other one? I do not understand at which point objects will be created, moved, deleted etc.
Greetings
Finn
I can see at least two problems:
DenseVector &DenseVector::operator=(const DenseVector &other) {
this->size = other.size;
memcpy(values, other.values, size * sizeof(double));
return *this;
}
You are not checking whether you have enough space in this->values. If this->size is smaller than other->size, you need to reallocate.
DenseVector::DenseVector(DenseVector &&other) : values(other.values),
size(other.size){}
DenseVector &DenseVector::operator=(DenseVector &&other) {
this->values = other.values;
this->size = other.size;
return *this;
}
In both cases you end up with two pointers pointing to the same memory. Now when you destroy both vectors, you get a double delete. You need to have other->values = nullptr; in both functions.
A better way to fix both issues it to use std::vector and rely on the rule of zero.
You have several problems.
The one in destructor is that delete should match with new not new[].
You have to use delete[] here:
DenseVector::~DenseVector() {
delete [] values;
}
Note: Deleting null pointer is fine, no test needed. setting values to nullptr is useless too, as reading the value after the object is destroyed is UB anyway.
Your move constructor doesn't move, but shallow copy, so you will have double delete, it should be
DenseVector::DenseVector(DenseVector &&other) /*noexcept*/: values(other.values), size(other.size)
{
other.vlalues = nullptr;
other.size = 0;
}
You have many problems:
first you should move objects in move-ctor and leave the moved-from in a valid state:
DenseVector::DenseVector(DenseVector &&other) : values(std::move(other.values)), size(std::move(other.size))
{
other.values = nullptr;
}
You should use ctor-init list to initialize member data rather than assign to them inside ctor body after being default-init in ctor-init-list.
DenseVector::DenseVector(int size) : size(size),
values(new double[size]{0})
{
}
Also in the destructor you are freeing an array of doubles with delete which is Undefined Behavior so use it this way:
delete[] values;
You don't need to check in the dtor whether values is nullptr or not:
delete[] values;

Fill vector with constructor call

I have two classes, one derive from the other. I would like to allocate an std:vector which is fill of the derived class. The tricky question is that I want it to call the move constructor written in the base class.
Here is the code:
class Base{
public:
size_t size;
double* buff;
Base(size_t _size):size(_size){
buff = new double[size];
}
Base() = delete;
Base operator=(const Base&) = delete;
Base(const Base&) = delete;
Base(Base&& b):size(b.size), buff(b.buff){
b.buff = nullptr;
}
Base operator=(Base&& b){
size = b.size;
buff = b.buff;
b.buff = nullptr;
}
};
class Derive : public Base{
public:
Derive(size_t _size):Base(_size){};
Derive() = delete;
Derive operator=(const Derive&) = delete;
Derive(const Derive&) = delete;
Derive(Derive&& b):Base(move(b)){}
Derive operator=(Derive&& b){
Base::operator=(move(b));
}
};
/********/
vector<Derive> v(10, move(Derive(5)));
g++ is telling me
error: use of deleted function ‘Derive::Derive(const Derive&)’
{ ::new(static_cast<void*>(__p)) _T1(std::forward<_Args>(__args)...); }
and I don't understand what I am supposed to do.
The problem here is that std::vector(count, object) copies object, count times, into the vector. You cannot move from it since you can only move an object into a single object.
If your class is not copyable then you will not be able to use it. You can however use
std::vector<Type> foo;
foo.reserve(count);
for (int i = 0; i < count; ++i)
foo.emplace_back(Types_parmeters);
You are trying to build a vector of 10 elements from one single source. This is not possible because once the source element has been moved once, it is left in an undeterminated state.
For that reason, this constructor is specified in draft n4296 for C++14 to use the copy constructor (emphasize mine):
23.3.6.2 vector constructors, copy, and assignment [vector.cons]
...vector(size_type n, const T& value,
const Allocator& = Allocator());
Effects: Constructs a vector with n copies of value, using the specified allocator.
Requires: T shall be CopyInsertable into *this.
...
An alternative solution is to write your own version of fill_n that can handle this use case. The typical fill_n does copies the same as the vector constructor, but we can write a more modern style one.
template <class T, class OutputIt, class Size, class .. Args>
OutputIt emplace_fill_n(OutputIt first, Size count, const Args& ... args)
{
for (Size i = 0; i != count; ++i) {
*first = T(args...);
++first;
}
}
Usage:
vector<Derive> v();
v.reserve(10);
emplace_fill_n<Derive>(back_inserter(v), 10);

How to realloc in c++?

The following code constitutes a MCVE, this reproduces the problem I want to ask about but it's not the real code. The real code is quite more complicated so that's why I wrote this for a demonstration of the problem.
The key feature I am looking for is to be able to grow a dynamically allocated array, please do not suggest using the stl because it's explicitly forbidden. This code is for educational purpose and thus there are restrictions.
#include <cstring>
#include <iostream>
class Value
{
public:
Value(int value = 0);
Value(const Value &value);
Value &operator =(const Value &other);
~Value();
operator int() {return *m_absurdPointer;}
private:
int *m_absurdPointer;
};
Value::Value(int value) :
m_absurdPointer(new int[1])
{
*m_absurdPointer = value;
}
Value::Value(const Value &value)
{
m_absurdPointer = new int[1];
memcpy(m_absurdPointer, value.m_absurdPointer, sizeof(*m_absurdPointer));
}
Value &Value::operator =(const Value &other)
{
m_absurdPointer = new int[1];
memcpy(m_absurdPointer, other.m_absurdPointer, sizeof(*m_absurdPointer));
return *this;
}
Value::~Value()
{
delete[] m_absurdPointer;
}
class ValueArray
{
public:
ValueArray();
~ValueArray();
void append(const Value &value);
void show() const;
private:
Value *m_array;
unsigned int m_capacity;
unsigned int m_length;
};
ValueArray::ValueArray() :
m_array(nullptr)
, m_capacity(0)
, m_length(0)
{
}
ValueArray::~ValueArray()
{
delete[] m_array;
}
void
ValueArray::append(const Value &value)
{
if (m_length >= m_capacity)
{
Value *newarray;
unsigned int unitSize;
unitSize = 1;
newarray = new Value[m_capacity + unitSize];
if ((m_capacity > 0) && (m_array != nullptr))
memcpy(newarray, m_array, m_capacity * sizeof(*m_array));
delete[] m_array;
m_array = newarray;
m_capacity += unitSize;
}
m_array[m_length++] = value;
}
void
ValueArray::show() const
{
for (size_t i = 0 ; i < m_length ; ++i)
std::cout << static_cast<int>(m_array[i]) << std::endl;
}
int
main(void)
{
ValueArray example;
for (int i = 0 ; i < 10 ; ++i)
example.append(Value(i));
example.show();
return 0;
}
It causes as you can see a double free issue, because the delete[] m_array; calls the destructor of the class Value after it has copied the values to the re-newed array.
I tried to do this with malloc()/realloc() but I need the destructor of Value() to be called so new is mandatory because I can't use free().
How to prevent this?, if I remove the delete[] m_absurdPointer; the double free would be gone of course but there would be a memory leak.
You basically want to implement an own vector class, right?
OK, first things first: As far as I know you cannot grow previously allocated memory. At least not with the standard allocator.
So you need to allocate a new, larger chunk of memory.
You can do this the standard way, using new:
Type * newdata = new Type[size];
In this case the constructor of the class Type will be called for each new element, which is size times.
To get your old data into that new array you need to copy or move it there:
for (size_t it = 0; it < oldsize; ++it) {
newdata[it] = olddata[it];
// newdata[it] = std::move(olddata[it]);
}
This is what std::copy resp. std::move are doing. (You could also use std::swap inside a loop.)
For that to work the Type class needs both a default constructor and a valid implementation of copy or move assignment.
You're using memcpy. In C++, this is generally a bad idea: Your implemented assignment operator isn't called, Therefore both the objects in your old array and the raw copies are using the same pointer, which is why you get that double free, obviously.
You could also allocate raw memory and use placement new to copy or move construct the new objects from the old ones:
void * memory = new char[size * sizeof(Type)];
for (size_t it = 0; it < oldsize; ++it) {
new (memory + it * sizeof(Type)) Type(olddata[it]); // copy
}
The above is only an example, for real code you need to consider alignment, too.
Finally, I'm sure you can somehow trick the default allocator to free your (old) memory without destructing the objects within, this allowing you to use the raw copy memcpy made. Though this would be a hack and could break on complex classes, it's not the C++ way of doing this.
The idiomatic way is to copy or move the old objects to the new storage (with either assignment or construction).
You should use the move-constructor if you have to stick with an vector-like implementation of ValueArray:
class Value
{
public:
Value(int value = 0);
Value(const Value &value);
Value(Value&& val);
Value &operator =(const Value &other);
Value &operator =(Value&& other);
~Value();
operator int() {return *m_absurdPointer;}
private:
int *m_absurdPointer;
};
Value::Value(Value&& o) : m_absurdPointer(o.m_absurdPointer) {
o.m_absurdPointer = nullptr;
}
Value &operator =(Value&& o) {
delete[] this->m_absurdPointer;
this->m_absurdPointer = o.m_absurdPointer;
o.m_absurdPointer = nullptr;
}
void
ValueArray::append(const Value &value)
{
if (m_length >= m_capacity)
{
Value *newarray;
unsigned int unitSize;
unitSize = 1;
newarray = new Value[m_capacity + unitSize];
if ((m_capacity > 0) && (m_array != nullptr)) {
std::move(m_array, m_array + m_length, newarray);
}
delete[] m_array;
m_array = newarray;
m_capacity += unitSize;
}
}

Move constructor for a custom container?

is the move constructor for a class that holds a dynamically allocated array supposed to delete it? For instance I have:
template<typename T>
class MyVector
{
public:
MyVector()
{
data = new T[32];
capacity = 32;
size = 0;
}
MyVector(const MyVector<T>& o) //The basic copy constructor
{
capacity = o.capacity;
size = o.size;
data = new T[capacity];
for(int i = 0; i < _size; i++)
{
data[i] = o.data[i];
}
}
MyVector(const MyVector<T>&& o) //The move constructor
{
//What goes here?
}
private:
T* data;
unsigned int size;
unsigned int capacity;
};
I understand the move constructor is called when I do something like:
MyVector A;
A = MyVector(); //The one on the right gets a move constructor called?
So is it supposed to be identical to the copy constructor?
is the move constructor for a class that holds a dynamically allocated array supposed to delete it? For instance I have:
There is nothing to delete. The object is being constructed after all. All it should do is take the resources from the object on the RHS. It should leave the RHS in a reasonable state. For example,
MyVector(MyVector<T>&& o)
{
data = o.data;
o.data = nullptr;
capacity = o.capacity;
o.capacity = 0;
size = o.size;
o.size = 0;
}
Note that, in order for a move constructor to work, the parameter cannot be const. A move constructor modifies its argument.