I am trying to initialize a dynamically created array of objects of a class. This class has both a default constructor, a two-parameter constructor and a destructor in addition to its members.
#include <someLibrary.h> // Some C library
class A {
// some member variables
public:
A() {}
A(unsigned p1, int p2)
{
initLibrary();
}
~A()
{
terminateLibrary();
}
// other member functions
}
int main()
{
unsigned nObj = 5;
A *obj = new A[nObj];
for (int i = 0; i<nObj; i ) {
obj[i] = A(i, -42); // destructor called here, when it shouldn't
}
// Do some things with member functions of objects
return 0;
// need destructor to be call only here
}
In the two-parameter constructor, I initialize a library. This constructor is called inside the for loop of the main()
function as you can see. In the destructor, I am terminating said library. I need to perform some actions pertaining to the library in the member functions of the objects. So, I can't terminate the library before the end of the main()
function. However, in my implementation (above), the destructor is called right after I initialize the object in the for loop.
I understand why in that this is simply an assignment rather than initialization. So, the destructor is called to destroy the temporary object which is created during assignment.
But, how can I achieve initialization of each object of a dynamically created array of objects using the two-paramter constructor without a destructor call?
CodePudding user response:
How can I achieve initialization of each object of a dynamically created array of objects using the two-parameter constructor without a destructor call?
Despite the other design flaws, to solve the temporary A
creation and assignment to the obj
, you should provide a setter function that sets the values p1
and p2
.
class A {
// some member variables
public:
void setValues(unsigned p1, int p2)
{
// ....
}
// ....
};
Now in the loop
for (int i = 0; i < nObj; i )
{
obj[i].setValues(i, -42);
}
That being said, I would recommend using a std::vector<A>
instead of managing the memory manually here.
#include <vector>
std::vector<A> obj(5);
for (A& a: obj) {
a.setValues(1, -42);
}
CodePudding user response:
A *obj = new A[nObj]; for (int i = 0; i<nObj; i ) { obj[i] = A(i, -42); // destructor called here, when it shouldn't }
You are creating an array with nObj
number of default-constructed objects, and then each loop iteration is creating a new temporary object and copy-assigning it to an array element. The destructor is called when each temporary object goes out of scope.
return 0; // need destructor to be call only here
Even if you were to rewrite the code to avoid the temporary objects, the destructors still won't be called, because you are not delete[]
'ing the array, and thus not destroying the objects in it. You are leaking the array. You should use std::vector
to avoid that.
In the two-parameter constructor, I initialize a library. This constructor is called inside the for loop of the
main()
function as you can see. In the destructor, I am terminating said library.
Why are you re-initializing the library on every object creation, and terminating the library on every object destruction? That makes no sense.
If you really need to handle the library like that, you should consider putting a static reference count in the class so only the first object created actually initializes the library, and the last object destroyed actually terminates the library. Then it won't matter how many objects you create in between, eg:
class A {
// some member variables
static size_t refCnt = 0;
public:
A()
{
if ( refCnt == 1)
initLibrary();
...
}
A(unsigned p1, int p2)
{
if ( refCnt == 1)
initLibrary();
...
}
A(const A&)
{
refCnt;
...
}
A(A&&)
{
refCnt;
...
}
~A()
{
...
if (--refCnt == 0)
terminateLibrary();
}
// other member functions
};
Alternatively:
class A {
// some member variables
struct libInit
{
libInit(){ initLibrary(); }
~libInit(){ terminateLibrary(); }
};
static std::shared_ptr<libInit> s_lib = std::make_shared<libInit>();
std::shared_ptr<libInit> m_lib;
public:
A()
{
m_lib = s_lib;
...
}
A(unsigned p1, int p2)
{
m_lib = s_lib;
...
}
A(const A& a)
{
m_lib = a.m_lib;
...
}
A(A&& a)
{
m_lib = std::move(a.m_lib);
...
}
~A()
{
...
}
// other member functions
};
Otherwise, consider moving the library initialization/cleanup out of your class altogether. It should be handled by main()
itself, either directly, or in another class that main()
creates only 1 object of.
I can't terminate the library before the end of the
main()
function.
All the more reason why the library initialization/cleanup doesn't really belong in your array to begin with. It should be handled in main()
before creating the array, and after destroying the array, eg:
int main()
{
initLibrary();
unsigned nObj = 5;
std::vector<A> obj(nObj);
for (A &elem : obj) {
elem = A(i, -42);
}
// Do some things with member functions of objects
obj.clear();
terminateLibrary();
return 0;
}
Alternatively:
#include <vector>
struct libInit
{
libInit(){ initLibrary(); }
~libInit(){ terminateLibrary(); }
};
int main()
{
libInit lib;
unsigned nObj = 5;
std::vector<A> obj(nObj);
for (A &elem : obj) {
elem = A(i, -42);
}
// Do some things with member functions of objects
return 0;
}
But, how can I achieve initialization of each object of a dynamically created array of objects using the two-paramter constructor without a destructor call?
The simplest way is to just add a method to your class, which you can call on the array's default-created objects, eg:
int main()
{
unsigned nObj = 5;
std::vector<A> obj(nObj);
for (A &elem : obj) {
elem.init(i, -42);
}
// Do some things with member functions of objects
return 0;
}
Otherwise, if you want to construct each array element using a non-default constructor, you would have to change the array to be just raw bytes of sufficient size, and then use placement-new
to construct the individual objects within those bytes at the appropriate offsets, eg:
#include <type_traits>
#include <memory>
#include <new>
class A {
...
};
int main()
{
unsigned nObj = 5;
std::vector< std::aligned_storage<sizeof(A), alignof(A)>::type > obj(nObj);
for (auto &elem : obj) {
new (&elem) A(i, -42);
}
// Do some things with member functions of objects
for (auto &elem : obj) {
A *a = std::launder(reinterpret_cast<A*>(&elem));
a->doThings();
}
// cleanup
for (auto &elem : obj) {
A *a = std::launder(reinterpret_cast<A*>(&elem));
std::destroy_at(a);
}
return 0;
}