Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Array class member initialization in C++

I have the following code snippet:

#include <iostream>
using namespace std;
class A {
    int* data;
    int size;
public:
    A(int s):size(s)
    {
        data = new int[size];
    }
    A() {
        data = nullptr;
    }
    ~A() {
        if (data) delete [] data;
    }
};
class B {
    A a[2];
public:
    B() {
        a[0] = A(10);
        a[1] = A(11); 
    }
};
int main(int argc, char *argv[]) {
    B b;
}

In the C++ code above, I have class A which has an array member int* data, and the (de)allocation of memory are handled by (de)constructor. The I created class B which has an array of class A of fixed length as a data member.

My question is: how to elegantly initialise the member A a[2]? In the code above, the A(10) and A(11) are created on the stack, when jumping out of the scope, their destructors will be called, hence the data comes invalid. When jumping of the main function's scope, the pointers held by a[2] will be deallocated twice, causing the error:

pointer being freed was not allocated.

One possible solution is to carefully design a copy constructor and a move constructor, by doing so the above coding paradigm could work.

Another solution I've tried is to initialise the array in the initialization list of class B:

B() : a { A(10), A(11) }

This solution works and I don't really tell the underlying mechanism of initialization list. I think it must be quite different from simply construct and copy. I really expected some experts could give an elaborate explanation of this mechanism. Of course, this solution is ugly hard-coded and not flexible.

So I wonder if there are some programming paradigms in C++ to tackle this design problem?

like image 245
Simon X. M. LIU Avatar asked Mar 12 '23 12:03

Simon X. M. LIU


1 Answers

In the code above, the A(10) and A(11) are created on the stack

They are temporary objects. It is not specified where they are created or if they're created at all.

when jumping out of the scope, their destructors will be called

The destructor of each temporary will be called after the corresponding move assignment statement ends.

One possible solution is to carefully design a copy constructor and a move constructor, by doing so the above coding paradigm could work.

And {copy,move} assignment operator too. You should always do that when the implicitly declared ones don't do the right thing. And they never do the right thing if you delete something in the destructor.

Another solution I've tried is to initialise the array in the initialization list of class B

This solution works and I don't really tell the underlying mechanism of initialization list. I think it must be quite different from simply construct and copy.

The bug in the original code is badly behaving move assignment operator of A. Since the initialization list never move assigns from a temporary, it never triggers the bug.

This is actually the more elegant way to construct a that you asked for. Not because it avoids the bug, but because avoiding unnecessary moving is good thing, intrinsically.

So I wonder if there are some programming paradigms in C++ to tackle this design problem?

Yes. RAII and Single responsibility principle. Unless your class does nothing else, besides managing the memory pointed by data, it should not be managing the memory. Instead, it should delegate the memory management to a RAII object. In this case, you should use a std::vector member.

class A {
    std::vector<int> data;
public:
    A(int s):data(s) {}
    A() = default;
};
like image 129
eerorika Avatar answered Mar 20 '23 11:03

eerorika