I recently ran into this problem at work. A library I'm using makes use of reference counted objects and implements its own way of handling it. Part of the implementation is that each class of the library has a private destructor. Im guessing this is to prevent creation of objects on the stack as the library manages objects lifetime automatically (Its a scene graph).
Anyways, I wanted to allocate an array of such a class on the heap and ran into the following problem:
#include <iostream>
using namespace std;
class test
{
public:
test() {
cout << "ctor" << endl;
}
//~test() = delete; also doesnt work
private:
~test()
{
cout << "dtor" << endl;
}
};
int main()
{
//works
auto oneInstance = new test;
//doesnt work
auto manyInstances = new test[3];
}
The array allocation produces the following error using GCC:
source_file.cpp: In function ‘int main()’:
source_file.cpp:15:5: error: ‘test::~test()’ is private
~test()
^
source_file.cpp:26:36: error: within this context
auto manyInstances = new test[3];
^
Why does the destructor need to be public/available in order to allocate an array of this class on the heap? It works fine when only allocating a single instance like in the line before. I also tried using the more modern "delete" syntax, but it produced the same result.
Is there any kind of magic in the new[] operator that I'm not aware of?
EDIT:
Thanks for the quick help. Im wondering why this code doesnt print "dtor" twice though:
#include <iostream>
using namespace std;
class test
{
public:
test() {
static int allocations = 0;
++allocations;
if(allocations == 3)
{
//produce exception
throw 1;
}
cout << "ctor" << endl;
}
~test()
{
cout << "dtor" << endl;
}
};
int main()
{
//works
auto oneInstance = new test;
//doesnt work
try {
auto manyInstances = new test[3];
}
catch(...)
{
cout << "error?";
}
}
This prints:
ctor ctor dtor error?
It's because of exceptions, the array version of new[]
has to go and call the destructor on elements that have been previously allocated when an exception propagates to ensure exception safety. The single element new
does not need to do that. If allocation fails, it just fails, no need to destroy anything.
§ 8.3.4 New [expr.new/20]
If the new-expression creates an array of objects of class type, the destructor is potentially invoked
Regarding your edit, see the following quote from the C++17 standard
§ 8.17 Throwing an exception [expr.throw/4]
If no exception is presently being handled, evaluating a throw-expression with no operand calls
std::terminate()
Regarding your second edit, you have missed counting an instance of test
that you created via new
(not new[]
), that leads to the first instance of test
being created, that's where the confusion about number of constructions comes from.