#include <iostream>
using namespace std;
class Car
{
public:
~Car() { cout << "Car is destructed." << endl; }
};
class Taxi :public Car
{
public:
~Taxi() {cout << "Taxi is destructed." << endl; }
};
void test(Car c) {}
int main()
{
Taxi taxi;
test(taxi);
return 0;
}
this is output:
Car is destructed.
Car is destructed.
Taxi is destructed.
Car is destructed.
I use MS Visual Studio Community 2017(Sorry, I don't know how to see the Visual C++'s edition).
When I used debug mode. I find one destructor is executed when leaving thevoid test(Car c){ }
function body as expected. And an extra destructor appeared when the test(taxi);
is over.
The test(Car c)
function uses value as formal parameter.
A Car is copied when going to the function.
So I thought there will be only one "Car is destructed" when leaving the function.
But actually there are two "Car is destructed" when leaving the function.(the first and second line as showed in the output)
Why are there two "Car is destructed"? Thank you.
===============
when I add a virtual function in class Car
for example:virtual void drive() {}
Then I get the expected output.
Car is destructed.
Taxi is destructed.
Car is destructed.
No, constructor does not return any value. While declaring a constructor you will not have anything like return type. In general, Constructor is implicitly called at the time of instantiation. And it is not a method, its sole purpose is to initialize the instance variables.
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.
Whenever a call to destructor is made , the allocated memory to the object is not released but the object is no longer accessible in the program. But delete completely removes the object from memory.
Default destructors call destructors of member objects, but do NOT delete pointers to objects.
It looks like the Visual Studio compiler is taking a bit of a shortcut when slicing your taxi
for the function call, which ironically results in it doing more work than one might expect.
First, it's taking your taxi
and copy-constructing a Car
from it, so that the argument matches.
Then, it's copying the Car
again for the pass-by-value.
This behaviour goes away when you add a user-defined copy constructor, so the compiler seems to be doing this for its own reasons (perhaps, internally, it's a simpler code path), using the fact that it is "allowed" to because the copy itself is trivial. The fact that you can still observe this behaviour using a non-trivial destructor is a bit of an aberration.
I don't know the extent to which this is legal (particularly since C++17), or why the compiler would take this approach, but I would agree that it's not the output I would have intuitively expected. Neither GCC nor Clang do this, though it may be that they do things the same way but are then better at eliding the copy. I have noticed that even VS 2019 is still not great at guaranteed elision.
When you create a Taxi
, you also create a Car
subobject. And when the taxi gets destroyed, both objects are destructed. When you call test()
you pass the Car
by value. So a second Car
gets copy-constructed and will get destructed when test()
is left. So we have an explanation for 3 destructors: the first and the two last in the sequence.
The fourth destructor (that is the second in the sequence) is unexpected and I couldn't reproduce with other compilers.
It can only be a temporary Car
created as source for the Car
argument. Since it doesn't happen when providing directly a Car
value as argument, I suspect it is for transforming the Taxi
into Car
. This is unexpected, since there is already a Car
subobject in every Taxi
. Therefore I think that the compiler does make an unnecessary conversion into a temp and doesn't do the copy elision that could have avoided this temp.
Clarification given in the comments:
Here the clarification with reference to the standard for language-lawyer to verify my claims:
[class.conv.ctor]
, i.e. constructing an object of one class (here Car) based on an argument of another type (here Taxi). Car
value. The compiler would be allowed to make a copy elision according [class.copy.elision]/1.1
, since instead of constructing a temporary, it could construct the value to be returned directly into the parameter.I could now reproduce your case by using the same compiler and draw an experiment to confirm what is going on.
My assumption above was that the compiler selected a suboptimal parameter passing process, using the constructor conversion Car(const &Taxi)
instead of copy constructing directly from the Car
subobject of Taxi
.
So I tried calling test()
but explicitly casting the Taxi
into a Car
.
My first attempt did not succeed to improve the situation. The compiler still used the suboptimal constructor conversion:
test(static_cast<Car>(taxi)); // produces the same result with 4 destructor messages
My second attempt succeeded. It does the casting as well, but uses pointer casting in order to strongly suggest the compiler to use the Car
subobject of the Taxi
and without creating this silly temporary object:
test(*static_cast<Car*>(&taxi)); // :-)
And surprise: it works as expected, producting only 3 destruction message :-)
Concluding experiment:
In a final experiment, I provided a custom constructor by conversion:
class Car {
...
Car(const Taxi& t); // not necessary but for experimental purpose
};
and implement it with *this = *static_cast<Car*>(&taxi);
. Sounds silly, but this also generates code that will only display 3 destructor messages, thus avoiding the unnecessary temporary object.
This leads to think that there could be a bug in the compiler that causes this behavior. It is af is the possibility of direct copy-constructing from the base class would be missed in some circumstances.
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