1

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.

vesii
  • 2,760
  • 4
  • 25
  • 71
  • 3
    Please provide a [mcve] – 463035818_is_not_an_ai Jan 15 '20 at 11:07
  • Why don't you a signed int instead of an unsigned int? The size parameter would then never be lower than 0. – Bktero Jan 15 '20 at 11:11
  • There are different ways to solve this problem. It is impossible to tell what you want without a [mre]. – L. F. Jan 15 '20 at 11:23
  • Does this answer your question? [Object array initialization without default constructor](https://stackoverflow.com/questions/4754763/object-array-initialization-without-default-constructor) – Daniel Langr Jan 15 '20 at 11:24
  • I have added a minimal example, please check it if you can – vesii Jan 15 '20 at 11:29
  • I suggest changing the definition of Data to be class `Data { private: int length; public: Data(int length=0) : length(length) {} };` – asmmo Jan 15 '20 at 12:07
  • Off-topic: `if (size < 0) { throw ArrayExceptions::InvalidLength(); }` – well, right. Negative sizes are meaningless. But you wouldn't have to care for if you would have used *unsigned* type right from the start. That's what these types are for. And most appropriate type for size/length specifications is `size_t`... – Aconcagua Jan 15 '20 at 12:29
  • You should get used to consistently implement the constructors initialiser list (not to be confused with `std::initializer_list`): `Array(size_t size, ...) : size(size), array(new T[size]) { }` (assuming you made the exception obsolete already; if not, then, of course, you cannot create the array there). You this way prefer direct initialisation by value over default initialisation + later asignment (which can be more costly for complex types). Some types *only* can be initialised that way (references, const members, non-default-constructible types). – Aconcagua Jan 15 '20 at 12:34

2 Answers2

1

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.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • This is the only place where I use `Array`. So what would be the fastest solution? Would it be to make changes in `Array`? If so, how should I do it? – vesii Jan 15 '20 at 11:38
  • 3
    The fastest, safest and best solution would be to let the standard library do the work for you and use `std::vector`, you could probably even just replace your `Array` class with `std::vector` – Alan Birtles Jan 15 '20 at 11:40
  • unfortunately, I can't use the `std` library. I thought of changing the arguments of Array and the call of `new`? – vesii Jan 15 '20 at 11:41
  • 2
    If you can't use the standard library (I don't understand why people trying to teach c++ don't let their students use c++) then you'll need to look here for how to do it manually: https://stackoverflow.com/questions/4754763/object-array-initialization-without-default-constructor – Alan Birtles Jan 15 '20 at 11:42
  • @AlanBirtles Actually I don't think it's that a bad idea if students learn about the internals of `std::vector` and similar. My criticism rather is that they go over to that *far* too early (and using int instead of size_t is a proof for that this has happened here as well...); that shouldn't be covered at all before using the standard library has got just as natural as using a tooth brush in the morning... – Aconcagua Jan 15 '20 at 17:41
0

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]);
    }
};
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • Hmm nice idea. Why do you use `free` instead of `delete`? Also, how should the `operator=` look like? – vesii Jan 15 '20 at 12:02
  • @vesii because `free` goes with `aligned_alloc`. It's undefined behaviour to `delete` (or `delete[]`) a `void *` – Caleth Jan 15 '20 at 12:08
  • I'm actually new to `aligned_alloc`. Is it similar to `malloc`? If so, it is possible to switch it to `malloc`? – vesii Jan 15 '20 at 17:21
  • @vesii Yes, it does something similar to malloc, but also ensures the result has the specified alignment. This is necessary for over-aligned `T`s – Caleth Jan 15 '20 at 17:41
  • I see. So I guess it is not possible to change to malloc? Also I get an error "An attribute list cannot appear here" for `alignas(alignof(T)) char[sizeof(T)]`. – vesii Jan 15 '20 at 18:12
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/206020/discussion-between-caleth-and-vesii). – Caleth Jan 15 '20 at 18:26