Rasula
Rasula

Reputation: 411

C++ destructor with return

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

Answers (11)

songyuanyao
songyuanyao

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

Stamatis Liatsos
Stamatis Liatsos

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

Francesco Dondi
Francesco Dondi

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

Niall
Niall

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);

  • Composition, it is an automatic member variable (i.e. "stack based")
  • A std::unique_ptr<> to express exclusive ownership of the dynamic object
  • A std::shared_ptr<> to express shared ownership of a dynamic object

Given 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

Hasson
Hasson

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

Trantor
Trantor

Reputation: 766

Of course not. Explicit call of 'return' ist 100% equivalent to implicit returning after execution of the destructor.

Upvotes: 1

M.M
M.M

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

Sean
Sean

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

Vlad from Moscow
Vlad from Moscow

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

Ajay
Ajay

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

Drax
Drax

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

Related Questions