EXP0
EXP0

Reputation: 882

Does explicitly calling destructor result in Undefined Behavior here?

In my opinion, the following code (from some C++ question) should lead to UB, but the it seems it is not. Here is the code:

#include <iostream>
using namespace std;
class some{ public: ~some() { cout<<"some's destructor"<<endl; } };
int main() { some s; s.~some(); }

and the answer is:

some's destructor
some's destructor

I learned form c++ faq lite that we should not explicitly call destructor. I think after the explicitly call to the destructor, the object s should be deleted. The program automatically calls the destructor again when it's finished, it should be UB. However, I tried it on g++, and get the same result as the above answer.

Is it because the class is too simple (no new/delete involved)? Or it's not UB at all in this case?

Upvotes: 5

Views: 1957

Answers (9)

5ound
5ound

Reputation: 1199

It is undefined behaviour. The undefined behaviour is the double destructor call and not with the destructor call itself. If you modify your example to:

#include <iostream>
using namespace std;
class some{ public: ~some() { [INSERT ANY CODE HERE] } };
int main() { some s; s.~some(); }

where [INSERT ANY CODE HERE] can be replaced with any arbitrary code. The results have unpredictable side effects, which is why it is considered undefined.

Upvotes: 0

James McNellis
James McNellis

Reputation: 355267

The behavior is undefined because the destructor is invoked twice for the same object:

  • Once when you invoke it explicitly
  • Once when the scope ends and the automatic variable is destroyed

Invoking the destructor on an object whose lifetime has ended results in undefined behavior per C++03 §12.4/6:

the behavior is undefined if the destructor is invoked for an object whose lifetime has ended

An object's lifetime ends when its destructor is called per §3.8/1:

The lifetime of an object of type T ends when:

— if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or

— the storage which the object occupies is reused or released.

Note that this means if your class has a trivial destructor, the behavior is well-defined because the lifetime of an object of such a type does not end until its storage is released, which for automatic variables does not happen until the end of the function. Of course, I don't know why you would explicitly invoke the destructor if it is trivial.

What is a trivial destructor? §12.4/3 says:

A destructor is trivial if it is an implicitly-declared destructor and if:

— all of the direct base classes of its class have trivial destructors and

— for all of the non-static data members of its class that are of class type (or array thereof), each such class has a trivial destructor.

As others have mentioned, one possible result of undefined behavior is your program appearing to continue running correctly; another possible result is your program crashing. Anything can happen and there are no guarantees whatsoever.

Upvotes: 15

TheJuice
TheJuice

Reputation: 4484

Can you define the undefined behaviour you expect? Undefined doesn't mean random (or catastrophic): the behaviour of a given program may be repeatable between invocations, it just means you can't RELY on any particular behaviour because it is undefined and there is no guarantee of what will happen.

Upvotes: 0

Edward Strange
Edward Strange

Reputation: 40897

I believe that if you want your code to be OK you simply need to call placement new and fill it back in before exiting. The call to the destructor isn't the issue, it's the second call to the destructor made when you leave scope.

Upvotes: 0

a1ex07
a1ex07

Reputation: 37382

From http://www.devx.com/tips/Tip/12684

Undefined behavior indicates that an implementation may behave unpredictably when a program reaches a certain state, which almost without exception is a result of a bug. Undefined behavior can be manifested as a run time crash, unstable and unreliable program state, or--in rare cases--it may even pass unnoticed.

In your case it doesn't crash because the destructor doesn't manipulate any field; actually, your class doesn't have any data members at all. If it did and in destructor's body you manipulated it in any way, you would likely get a run-time exception while calling destructor for the second time.

Upvotes: 3

Tomaka17
Tomaka17

Reputation: 4892

What the main function does is reserving space on the stack, calling some's constructor, and at the end calling some's destructor. This always happens with a local variable, whatever code you put inside the function. Your compiler won't detect that you manually called the destructor.

Anyway you should never manually call an object's destructor, except for objects created with placement-new.

Upvotes: 0

Jerry Coffin
Jerry Coffin

Reputation: 490603

It's undefined behavior -- but as with any UB, one possibility is that it (more or less) appears to work, at least for some definition of work.

Essentially the only time you need (or want) to explicitly invoke a destructor is in conjunction with placement new (i.e., you use placement new to create an object at a specified location, and an explicit dtor invocation to destroy that object).

Upvotes: 6

Justin Ethier
Justin Ethier

Reputation: 134257

It most likely works fine because the destructor does not reference any class member variables. If you tried to delete a variable within the destructor you would probably run into trouble when it is automatically called the second time.

Then again, with undefined behavior, who knows? :)

Upvotes: 1

JaredPar
JaredPar

Reputation: 755317

The problem here is that deletion / deallocation and destructors are separate and independent constructs. Much like new / allocation and constructors. It is possible to do only one of the above without the other.

In the general case this scenario does lack usefulness and just lead to confusion with stack allocated values. Off the top of my head I can't think of a good scenario where you would want to do this (although I'm sure there is potentially one). However it is possible to think of contrived scenarios where this would be legal.

class StackPointer<T> {
  T* m_pData;
public:
  StackPointer(T* pData) :m_pData(pData) {}
  ~StackPointer() { 
    delete m_pData; 
    m_pData = NULL; 
  }
  StackPointer& operator=(T* pOther) {
    this->~StackPointer();
    m_pData = pOther;
    return this;
  }
};

Note: Please don't ever code a class this way. Have an explicit Release method instead.

Upvotes: 1

Related Questions