-1

I'm experimenting with calling an object's destructor explicitly. The following code works as expected:

class Foo {
    public:

    ~Foo() {
        x_=x_+10;
        std::cout << "x_ = " << x_ << std::endl;
    }

    int x() {
        return x_;
    }
    int x_=0;
};


int main()
{
    Foo f;
    std::cout << "f.x() = " << f.x() << std::endl;

    f.~Foo();
    f.~Foo();

    std::cout << "f.x() = " << f.x() << std::endl;
    return 0;
}

And the printout is:

f.x() = 0                                                                                                                                                      
x_ = 10                                                                                                                                                        
x_ = 20                                                                                                                                                        
f.x() = 20                                                                                                                                                     
x_ = 30

As expected, every time the destructor is called, x_ is incremented by 10, so we see the progression from 10 to 20 to 30.

However, if we remove the std::cout from the destructor, such as the following:

class Foo {
    public:

    ~Foo() {
        x_=x_+10;
    }

    int x() {
        return x_;
    }
    int x_=0;
};


int main()
{
    Foo f;
    std::cout << "f.x() = " << f.x() << std::endl;

    f.~Foo();
    f.~Foo();

    std::cout << "f.x() = " << f.x() << std::endl;
    return 0;
}

Then the printout becomes:

f.x() = 0                                                                                                                                                      
f.x() = 0 

The increment in the destructor no longer works. Can someone explain why the behavior of the destructor can be affected by a print statement?

OneZero
  • 11,556
  • 15
  • 55
  • 92
  • 1
    Calling destructors explicitly is undefined behavior most of the time. You should use that very consciously only (e.g. in context of advanced memory allocation pools). IIRC there are some valid uses applied with the Qt framework. – πάντα ῥεῖ Oct 20 '18 at 19:12
  • The ONLY time calling a destructor explicitly is legal is if the object was allocated with **placement-new**, since you can't use `delete` to free the object in that case. For example: `char buf[sizeof(Foo)]; Foo *f = new(buf) Foo; ... ; f->~Foo();` – Remy Lebeau Oct 20 '18 at 19:33
  • Your code has introduced undefined behaviour in two ways: (1) Invoking a destructor twice on an object. (2) Calling a non-static member of an object after the destructor. Explaining what is actually seen, with particular compilers, optimisation settings, or phases of the moon is pointless, since the observed behaviour may differ with other compilers, optimisation settings, or phases of the moon. – Peter Oct 20 '18 at 19:43
  • You can look at the assembly to find out what the compiler generated from the broken source code. – Christian Hackl Oct 20 '18 at 22:12
  • There's not really any point in trying to explain undefined behavior, as it's probably just a result of the crazily complicated things compilers do to optimize your code. I've heard code with UB being affected by `if (false) { \*...*\ }`. – eesiraed Oct 21 '18 at 00:27

3 Answers3

0
f.~Foo();
f.~Foo();

Don't. Just don't. This is bad. Very bad. Never do that, especially when you don't understand what a destructor is.

The compiler is just skipping the code, because this is uncharted territory and it does whatever it wants in that case.

Explicit destructor call is not working

Matthieu Brucher
  • 21,634
  • 7
  • 38
  • 62
0

Using a destroyed object is undefined behaviour. Thus any changes to the object stste in a destructor are not observable after the destructor ends.

When you increment by 10 ina destructor, the compiler can easily prove this is not observable, so there is no need to waste time doing it at runtime.

When you print afterwards, it could copy the value, add 10, then print the copy. But the compiler instead increments it in place and prints it.

Programs that have undefined behaviour have no constraints on their behaviour, both before and after the undefined behaviour, under the C++ standard.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0
{
    Foo f;
    f.~Foo();
}

This is undefined behavior. f is a variable with automatic storage duration. That means that its lifetime is managed automatically and you can't explicitly end its life.

A program that has undefined behavior can behave in any way, so you can't rely on anything you observe a UB program do.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • The life of f is explicitly terminated at the scope closure ... the '}' – 2785528 Oct 20 '18 at 21:17
  • @2785528 no, it's implicitly terminated. – bolov Oct 20 '18 at 21:43
  • Not the way I use it ;) – 2785528 Oct 20 '18 at 22:04
  • @2785528 it's not about how you use it. It's about what the standard says, i.e. how C++ works. When control exits a scope the lifetime of objects from that scope with automatic storage duration ends. It's implicit. It could be on `}`, it could be on a `return`, it could be on an exception. And you don't have to explicitly end the life of the variable. It's implicit as I've stated above. – bolov Oct 20 '18 at 22:21
  • 2 of 4 answers of this post: https://stackoverflow.com/q/51970988/2785528 "Clearing stack memory allocated in main()?" show the 'explicit' insert of a close-brace to trigger the 'implicit' actions (of dtors) to free some automatic memory for re-use. (explicit: stated clearly and in detail, leaving no room for confusion or doubt) (implicit: implied though not plainly expressed) (The other 2 of 4 are in error.) – 2785528 Oct 22 '18 at 18:47
  • @2785528 C++ standard: "§12.4 Destructors [class.dtor] Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended (3.8). [ Example: if the destructor for an automatic object is explicitly invoked, and the block is subsequently left in a manner that would ordinarily invoke implicit destruction of the object, the behavior is undefined. — end example ] " – bolov Oct 22 '18 at 19:28