Search code examples
c++constructordynamic-memory-allocation

new array of objects with constructor that needs parameters in C++


Before I start with my question, let me say that I know that I can solve my problem easily using the standard library or gsl library (e.g. std::Vector). But I'm trying to learn C++ and dynamic memory allocation, so I would like to know if there is a way to solve it without using a Vector or anything similar.

I have a Layer class that contains a number of neurons determined when the object is created (through dynamic memory allocation):

class Layer {
private:
    unsigned int _size;
    const Neuron* _neurons;

public:
    Layer(unsigned int size);
    ~Layer();

    const Neuron* getNeuron(unsigned int index) const;
};

Layer::Layer(unsigned int size) {
    _size = size;
    _neurons = new Neuron[size];
}

const Neuron* Layer::getNeuron(unsigned int index) const {
    Expects(index < _size);

    return &_neurons[index];
}

Layer::~Layer() {
    delete[] _neurons;
}

Now the challenge is in the Network class: I need to create a Network of n layers, each containing a different number of neurons (passed as an array of length n):

class Network {
private:
    unsigned int _nb_layers;
    const Layer* _layers;

public:
    Network(unsigned int nb_layers, unsigned int* nb_neurons);
    ~Network();

    const Layer* getLayer(unsigned int index) const;
};

Network::Network(unsigned int nb_layers, unsigned int *nb_neurons) {
    _nb_layers = nb_layers;
    _layers = new Layer[nb_layers];

    for(int i = 0; i < nb_layers; i++) {
        _layers[i] = new Layer(nb_neurons[i]);
    }
}

const Layer* Network::getLayer(unsigned int index) const {
    Expects(index < _nb_layers);

    return &_layers[index];
}

Network::~Network() {
    delete _layers;
}

The problem is that the Network constructor fails, because _layers = new Layer[nb_layers] tries to call the constructor with no parameters, which fails. Also, _layers[i] = new Layer(nb_neurons[i]) fails because "No viable overloaded '='", which I do not understand. How can I fix this using dynamic memory allocation instead of std::Vector?

Finally, is the way I implemented dynamic memory allocation correct and without any memory leak? I'm wondering what happens to my unsigned int *nb_neurons in the Network constructor since the values are being used but I'm never deleting it. (As a background, I'm been coding in Java, Python and PHP for years)

Thank you very much!


Solution

  • I need to create a Network of n layers, each containing a different number of neurons (passed as an array of length n):

    You won't be able to do that with a fixed array of Layer objects. Although there is a syntax available for the new[] operator to initialize all of the elements of the allocated array with the same constructor value, there is no syntax for initializing them with different values, at least not when the values are coming from another array.

    To do what you are asking for, you will have to create an array of Layer* pointers (or better, an array of std::unique_ptr<Layer> objects in C++11 and later), and then dynamically create each Layer object with a different constructor value as needed. You were close in doing that, but you are just missing an extra layer of indirection in your array declaration:

    class Network {
    private:
        unsigned int _nb_layers;
        Layer** _layers; // <-- DYNAMIC ARRAY OF POINTERS, NOT OBJECTS!
    
    public:
        Network(unsigned int nb_layers, unsigned int* nb_neurons);
        ~Network();
    
        const Layer* getLayer(unsigned int index) const;
    };
    
    Network::Network(unsigned int nb_layers, unsigned int *nb_neurons) {
        _nb_layers = nb_layers;
        _layers = new Layer*[nb_layers]; // <-- ALLOCATE ARRAY OF POINTERS FIRST
    
        for(int i = 0; i < nb_layers; i++) {
            _layers[i] = new Layer(nb_neurons[i]); // <-- THEN MAKE EACH POINTER POINT TO AN OBJECT
        }
    }
    
    const Layer* Network::getLayer(unsigned int index) const {
        Expects(index < _nb_layers);
    
        return _layers[index];
    }
    
    Network::~Network() {
        for (int i = 0; i < _nb_layers; ++i)
            delete _layers[i]; // <-- NEED TO FREE EACH OBJECT FIRST
        delete[] _layers; // <-- THEN FREE THE ARRAY ITSELF
    }
    

    In C++11 and later, you can do this instead:

    class Network {
    private:
        unsigned int _nb_layers;
        std::unique_ptr<std::unique_ptr<Layer>[]> _layers;
    
    public:
        Network(unsigned int nb_layers, unsigned int* nb_neurons);
        // ~Network(); // <-- NOT NEEDED!
    
        const Layer* getLayer(unsigned int index) const;
    };
    
    Network::Network(unsigned int nb_layers, unsigned int *nb_neurons) {
        _nb_layers = nb_layers;
        _layers.reset( new std::unique_ptr<Layer>[nb_layers] );
    
        for(int i = 0; i < nb_layers; i++) {
            _layers[i].reset( new Layer(nb_neurons[i]) );
            // or, in C++14 and later:
            // _layers[i] = std::make_unique<Layer>(nb_neurons[i]);
        }
    }
    
    const Layer* Network::getLayer(unsigned int index) const {
        Expects(index < _nb_layers);
    
        return _layers[index].get();
    }
    
    /*
    Network::~Network()
    {
        // NOTHING TO DO HERE!
    }
    */
    
    

    And BTW, both of your classes are violating the Rule of 3/5/0. Neither of the classes is implementing a copy constructor or a copy assignment operator in order to make copies of their respective arrays from one class instance to another. Or, in the case of C++11 and later, a move constructor and a move assignment operator to move their respective arrays from one class instance to another.