Reputation: 411
In C++ if we define a class destructor as:
~Foo(){
return;
}
upon calling this destructor will the object of Foo
be destroyed or does
explicitly returning from the destructor mean that we don't ever want to destroy it.
I want to make it so that a certain object is destroyed only through another objects destructor i.e. only when the other object is ready to be destroyed.
Example:
class Class1{
...
Class2* myClass2;
...
};
Class1::~Class1(){
myClass2->status = FINISHED;
delete myClass2;
}
Class2::~Class2(){
if (status != FINISHED) return;
}
I searched online and couldn't seem to find an answer to my question. I've also tried figuring it out myself by going through some code step by step with a debugger but can't get a conclusive result.
Upvotes: 39
Views: 15660
Reputation: 172994
No, you can't prevent the object from being destroyed by return statement, it just means the execution of the dtor's body will end at that point. After that it still will be destroyed (including its members and bases), and the memory still will be deallocated.
You migth throw exception.
Class2::~Class2() noexcept(false) {
if (status != FINISHED) throw some_exception();
}
Class1::~Class1() {
myClass2->status = FINISHED;
try {
delete myClass2;
} catch (some_exception& e) {
// what should we do now?
}
}
Note it's a terrible idea indeed. You'd better to reconsider the design, I'm sure there must be a better one. Throwing exception won't stop the destruction of its bases and members, just make it possible to get the process result of Class2
's dtor. And what could be done with it is still not clear.
Upvotes: 47
Reputation: 74
For this case you could use a class-specific overload of the delete operator. So for you Class2 you could something like this
class Class2
{
static void operator delete(void* ptr, std::size_t sz)
{
std::cout << "custom delete for size " << sz << '\n';
if(finished)
::operator delete(ptr);
}
bool Finished;
}
Then if you set the finished to true before the delete, the actual deletion will be called. Note that i haven't tested it, i just modified the code that i've found here http://en.cppreference.com/w/cpp/memory/new/operator_delete
Class1::~Class1()
{
class2->Finished = true;
delete class2;
}
Upvotes: 0
Reputation: 1094
So, as all the others pointed out, return
is not a solution.
The first thing I would add is that you shouldn't usually worry about this. Unless your professor explicitly asked.
It would be very odd if you could't trust the external class to only delete your class at the right time, and I figure no one else is seeing it.
If the pointer is passed around, the pointer would very probably be shared_ptr
/weak_ptr
, and let it destroy your class at the right moment.
But, hey, it's good to wonder how we would solve an odd problem if it ever arose, if we learn something (and don't waste time while on a deadline!)
So what for a solution? If you can at least trust the destructor of Class1 not to destroy your object too early, then you can just declare the destructor of Class2 as private, and then declare the destructor of Class1 as friend of Class2, like this:
class Class2;
class Class1 {
Class2* myClass2;
public:
~Class1();
};
class Class2 {
private:
~Class2();
friend Class1::~Class1();
};
Class1::~Class1() {
delete myClass2;
}
Class2::~Class2() {
}
As a bonus, you don't need the 'status' flag; which is good -if someone wanted this bad to screw with you, why wouldn't set the status flag to FINISHED
anywhere else, and then call delete
?
This way, you have actual guarantee that the object can be destroyed nowhere else than in the destructor of Class1.
Of course, the destructor of Class1 gets access to all private members of Class2. It might not matter -after all, Class2 is about to be destroyed anyway! But if it does, we can conjure even more convoluted ways to work around it; why not. For instance:
class Class2;
class Class1 {
private:
int status;
Class2* myClass2;
public:
~Class1();
};
class Class2Hidden {
private:
//Class2 private members
protected:
~Class2Hidden();
public:
//Class2 public members
};
class Class2 : public Class2Hidden {
protected:
~Class2();
friend Class1::~Class1();
};
Class1::~Class1() {
delete myClass2;
}
Class2Hidden::~Class2Hidden() {
}
Class2::~Class2() {
}
This way the public members will still be available in the derived class, but the private members will actually be private. ~Class1 will only get access to the private and protected members of Class2, and the protected members of Class2Hidden; which in this case is only the destructors. If you need to keep a protected member of Class2 protected from the destructor of Class1... there are ways, but it really depends on what you are doing.
Good luck!
Upvotes: 1
Reputation: 30604
[D]oes explicitly returning from the destructor mean that we don't ever want to destroy it?
No. An early return (via return;
or throw ...
) only means the rest of the body of the destructor is not executed. The base and members are still destroyed and their destructors still run, see [except.ctor]/3.
For an object of class type of any storage duration whose initialization or destruction is terminated by an exception, the destructor is invoked for each of the object's fully constructed subobjects...
See below for code samples of this behaviour.
I want to make it so that a certain object is destroyed only through another objects destructor i.e. only when the other object is ready to be destroyed.
It sounds like the question is rooted in the issue of ownership. Deleting the "owned" object only once the parent is destroyed in a very common idiom and achieved with one of (but not limited to);
std::unique_ptr<>
to express exclusive ownership of the dynamic objectstd::shared_ptr<>
to express shared ownership of a dynamic objectGiven the code example in the OP, the std::unique_ptr<>
may be a suitable alternative;
class Class1 {
// ...
std::unique_ptr<Class2> myClass2;
// ...
};
Class1::~Class1() {
myClass2->status = FINISHED;
// do not delete, the deleter will run automatically
// delete myClass2;
}
Class2::~Class2() {
//if (status != FINISHED)
// return;
// We are finished, we are being deleted.
}
I note the if
condition check in the example code. It hints at the state being tied to the ownership and lifetime. They are not all the same thing; sure, you can tie the object reaching a certain state to it's "logical" lifetime (i.e. run some cleanup code), but I would avoid the direct link to the ownership of the object. It may be a better idea to reconsider some of the semantics involved here, or allow the "natural" construction and destruction to dictate the object begin and end states.
Side note; if you have to check for some state in the destructor (or assert some end condition), one alternative to the throw
is to call std::terminate
(with some logging) if that condition is not met. This approach is similar to the standard behavior and result when an exception is thrown when unwinding the stack as a result of an already thrown exception. This is also the standard behavior when a std::thread
exits with an unhandled exception.
[D]oes explicitly returning from the destructor mean that we don't ever want to destroy it?
No (see above). The following code demonstrates this behaviour; linked here and a dynamic version. The noexcept(false)
is needed to avoid std::terminate()
being called.
#include <iostream>
using namespace std;
struct NoisyBase {
NoisyBase() { cout << __func__ << endl; }
~NoisyBase() { cout << __func__ << endl; }
NoisyBase(NoisyBase const&) { cout << __func__ << endl; }
NoisyBase& operator=(NoisyBase const&) { cout << __func__ << endl; return *this; }
};
struct NoisyMember {
NoisyMember() { cout << __func__ << endl; }
~NoisyMember() { cout << __func__ << endl; }
NoisyMember(NoisyMember const&) { cout << __func__ << endl; }
NoisyMember& operator=(NoisyMember const&) { cout << __func__ << endl; return *this; }
};
struct Thrower : NoisyBase {
Thrower() { cout << __func__ << std::endl; }
~Thrower () noexcept(false) {
cout << "before throw" << endl;
throw 42;
cout << "after throw" << endl;
}
NoisyMember m_;
};
struct Returner : NoisyBase {
Returner() { cout << __func__ << std::endl; }
~Returner () noexcept(false) {
cout << "before return" << endl;
return;
cout << "after return" << endl;
}
NoisyMember m_;
};
int main()
{
try {
Thrower t;
}
catch (int& e) {
cout << "catch... " << e << endl;
}
{
Returner r;
}
}
Has the following output;
NoisyBase
NoisyMember
Thrower
before throw
~NoisyMember
~NoisyBase
catch... 42
NoisyBase
NoisyMember
Returner
before return
~NoisyMember
~NoisyBase
Upvotes: 17
Reputation: 1914
You can make a new method to make the object "commit suicide" and keep the destructor empty, so something like this will do the job you would like to do:
Class1::~Class1()
{
myClass2->status = FINISHED;
myClass2->DeleteMe();
}
void Class2::DeleteMe()
{
if (status == FINISHED)
{
delete this;
}
}
Class2::~Class2()
{
}
Upvotes: 1
Reputation: 766
Of course not. Explicit call of 'return' ist 100% equivalent to implicit returning after execution of the destructor.
Upvotes: 1
Reputation: 141638
~Foo(){
return;
}
means exactly the same as:
~Foo() {}
It is similar to a void
function; reaching the end without a return;
statement is the same as having return;
at the end.
The destructor contains actions that are performed when the process of destroying a Foo
has already begun. It's not possible to abort a destruction process without aborting the entire program.
Upvotes: 24
Reputation: 62532
No. return
just means exit the method, it doesn't stop the destruction of the object.
Also, why would you want to? If the object is allocated on the stack and you somehow managed to stop destruction then the object would live on a reclaimed part of the stack that will probably be overwritten by the next function call, which may write all over your objects memory and create undefined behavior.
Likewise, if the object is allocated on the heap and you managed to prevent destruction you'd have a memory leak as the code calling delete
would assume that it didn't need to hold on to a pointer to the object whilst it's actually still there and taking up memory that nobody is referencing.
Upvotes: 1
Reputation: 311088
According to the C++ Standard (12.4 Destructors)
8 After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct non-variant non-static data members, the destructors for X’s direct base classes and, if X is the type of the most derived class (12.6.2), its destructor calls the destructors for X’s virtual base classes. All destructors are called as if they were referenced with a qualified name, that is, ignoring any possible virtual overriding destructors in more derived classes. Bases and members are destroyed in the reverse order of the completion of their constructor (see 12.6.2). A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for the members and bases are called. Destructors for elements of an array are called in reverse order of their construction (see 12.6).
So a returning statement does not prevent the object for which the destructor is called to be destroyed.
Upvotes: 8
Reputation: 18431
All stack-based objects inside will get destructed, no matter how soon you return
from the destructor. If you miss to delete
dynamically allocated objects, then there would be intentional memory leak.
This is the whole idea how move-constructors would work. The move CTOR would simply take original object's memory. The destructor of original object simply wont/cant call delete
.
Upvotes: 1
Reputation: 13288
does explicitly returning from the destructor mean that we don't ever want to destroy it.
No.
The destructor is a function so you can use the return
keyword inside of it but that won't prevent the destruction of the object, once you are inside the destructor you are already destroying your object so any logic that wants to prevent that will have to occur before.
For some reason i intuitively think your design problem can be solved with a shared_ptr
and maybe a custom deleter, but that would require more info on the said problem.
Upvotes: 7