#include <iostream>
struct A { ~A(); };
A::~A() {
std::cout << "Destructor was called!" << std::endl;
}
typedef A AB;
int main() {
AB x;
x.AB::~AB(); // Why does this work?
x.AB::~A();
}
The output of the above program is:
Destructor was called!
Destructor was called!
Destructor was called!
I assume the first two lines belonging to user destructor calls, while the third line is due to the destructor being called when exiting the scope of main
function.
From my understanding a typedef is an alias for a type. In this case AB
is an alias for A
.
Why does this apply for the name of the destructor too? A reference to the language specification is very much appreciated.
Edit: This was compiled using Apple LLVM version 9.1.0 (clang-902.0.39.1) on macOS High Sierra Version 10.13.3.
If a base class A or a member of A has a destructor, and a class derived from A does not declare a destructor, a default destructor is generated. The default destructor calls the destructors of the base class and members of the derived class.
A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ). For example, the destructor for class String is declared: ~String() .
If the destructor of the base class is protected, you can not destroy derived objects using a base class pointer; therefore, the destructor must not be virtual. Only to make the point clear about types (not pointers or references): If the destructor of a class Base is private, you can not use the type.
Destructors are usually used to deallocate memory and do other cleanup for a class object and its class members when the object is destroyed. A destructor is called for a class object when that object passes out of scope or is explicitly deleted.
Aliases and typedefs (C++) You can use an alias declaration to declare a name to use as a synonym for a previously declared type. (This mechanism is also referred to informally as a type alias ). You can also use this mechanism to create an alias template, which can be particularly useful for custom allocators.
The notation for explicit call of a destructor can be used for any scalar type name. Allowing this makes it possible to write code without having to know if a destructor exists for a given type.
The rule is, that in a destructor call, the thing after the ~ is a type-name. int is not such a thing, but a typedef-name is. Look it up in 7.1.5.2.
Although the C practice of declaring a nameless structure in a typedef statement still works, it provides no notational benefits as it does in C. The preceding example declares a class named POINT using the unnamed class typedef syntax. POINT is treated as a class name; however, the following restrictions apply to names introduced this way:
Why does this apply for the name of the destructor too?
Because standard says:
[class.dtor]
In an explicit destructor call, the destructor is specified by a ~ followed by a type-name or decltype-specifier that denotes the destructor’s class type. ...
A typedef alias is a type-name which denotes the same class as the type-name of the class itself.
The rule even has a clarifying example:
struct B { virtual ~B() { } }; struct D : B { ~D() { } }; D D_object; typedef B B_alias; B* B_ptr = &D_object; void f() { D_object.B::~B(); // calls B's destructor B_ptr->~B(); // calls D's destructor B_ptr->~B_alias(); // calls D's destructor B_ptr->B_alias::~B(); // calls B's destructor B_ptr->B_alias::~B_alias(); // calls B's destructor }
Further specification about the name lookup, also with an example that applies to the question:
[basic.lookup.qual]
If a pseudo-destructor-name ([expr.pseudo]) contains a nested-name-specifier, the type-names are looked up as types in the scope designated by the nested-name-specifier. Similarly, in a qualified-id of the form:
nested-name-specifieropt class-name :: ~ class-name
the second class-name is looked up in the same scope as the first. [ Example:
struct C { typedef int I; }; typedef int I1, I2; extern int* p; extern int* q; p->C::I::~I(); // I is looked up in the scope of C q->I1::~I2(); // I2 is looked up in the scope of the postfix-expression struct A { ~A(); }; typedef A AB; int main() { AB* p; p->AB::~AB(); // explicitly calls the destructor for A }
— end example ]
Because when you write ~AB()
you are not naming or calling the destructor. You are writing ~
followed by the a name of the class, and the destructor call is automatically provisioned as a result of the specified semantics of writing those tokens next to each other.
Usually this is academic but here you see why it can matter.
Similarly, by writing AB()
you are not "calling a constructor", even though this looks like a function call and many language newcomers interpret the code this way. (This can lead to fun and games when attempting to call a template constructor without argument deduction: without being able to name the constructor, there's no way to provide those arguments!)
In fact, neither the constructor nor the destructor technically even have a name!
These nuances make C++ fun, right?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With