vesii
vesii

Reputation: 3128

Making class to call the right constructor when used

I have a class Array which has the following constructor:

explicit Array(int size = 0, const T &v = T()) {
    if (size < 0) {
        throw ArrayExceptions::InvalidLength();
    }
    Array::size = size;
    array = new T[size];
    insertValueIntoArray(size,v);
}

In some other class DataController I have:

Array<Data> dataArray;
int dataAmount;

explicit DataController(int length) : dataArray(length), dataAmount(length) {}

But Data.h does not have a constructor without arguments so the compiler complaints on const T &v = T() of Array.h:

error: no matching function for call to 'Data::Data()'

Instead it has the following constructor:

Data(int length) : length(length), /** Other constructor calls ...  **/ {}

What should I change in order to make the Array use the Data(length) constructor insead of the Data()? I can modify all the files.

I have tried to switch it to:

explicit DataController(int length) : dataArray(length, Data(length)), dataAmount(length) {}

but then I get the same error in line:

array = new T[size];

Minimal example:

template<typename T>
class Array {
    int size;
    T *array;

public:
    explicit Array(int size = 0, const T &value = T()) {
        Array::size = size;
        array = new T[size];
    }

    ~Array() {
        delete[] array;
    }
};

class Data {
private:
    int length;

public:
    Data(int length) : length(length) {}

};

class DataController {
private:
    Array<Data> dataArray;
    int dataAmount;

public:

    explicit DataController(int length) : dataArray(length), dataAmount(length) {}
};

Please suggest solutions without using the std if you can.

Upvotes: 1

Views: 114

Answers (2)

Caleth
Caleth

Reputation: 62636

Rather than calling new T[size], you can allocate uninitalised memory then copy-construct Ts into it.

template<typename T>
class Array {
    struct aligned_storage {
        struct type {
            alignas(alignof(T)) unsigned char data[sizeof(T)];
        };
    };
    using storage_t = aligned_storage::type;
    int size;
    storage_t * storage;

    void clear() {
        for (int i = 0; i < size; ++i)
            reinterpret_cast<T*>(storage.data[i])->~T();
        delete[] data;
    }

public:
    explicit Array(int size = 0, const T & value = T())
        : size(size), storage(new storage_t[size])
    {
        for (int i = 0; i < size; ++i)
            new (storage.data[i]) T(value);
    }

    Array(const Array & other) 
        : size(other.size), storage(new storage_t[other.size])
    {
        for (int i = 0; i < size; ++i)
            new (data[i]) T(other[i]);
    }

    Array(Array && other) 
        : size(other.size), storage(other.storage)
    {
        other.size = 0;
        other.data = nullptr;
    }

    Array& operator=(Array other) 
    {
        clear();
        storage = other.storage;
        size = other.size;
    }

    ~Array() {
        clear();
    }

    T& operator[](int pos) {
        return *reinterpret_cast<T*>(storage.data[pos]);
    }

    const T& operator[](int pos) const {
        return *reinterpret_cast<T*>(storage.data[pos]);
    }
};

Upvotes: 0

Alan Birtles
Alan Birtles

Reputation: 36379

You have 2 issues in your code.

The first is the default value of v in Array is T() which requires a default constructor. This is easily solved by just not using the default parameter:

dataArray(length, Data(length))

The second more complicated issue is then that:

array = new T[size]

calls the default constructor for each element in array. The simplest solution is to use an existing class that handles this for you like std::vector. If you want to implement it yourself you'll need to allocate memory using malloc, ::operator new or maybe std::aligned_storage then use placement new to initialise each element to a copy of v. You'll need to keep track of both how many elements you've allocated and how many you have initialised then call the destructors only for the initialised elements.

Upvotes: 1

Related Questions