I have a compound class (instance containing other instance, nor pointer, nor reference).
When the container instance is destroyed, destructor of contained instance is called (I am ok with that, it's logic). But the issue is that if the contained instance is stack allocated destructor is called once again when reaching out of scope.
Is that a coding error or a compiler issue?
What is the cleanest way of fixing it?
Here is my sample:
#include <iostream>
using std::cout;
using std::endl;
class A {
public:
int i;
A(int i_) : i(i_) {
cout << "A(): " << i << endl;
}
~A() {
cout << "~A(): " << i << endl;
}
};
class B {
public:
A a;
int b;
B(const A& a_) : a(a_) {
cout << "B(): " << a.i << endl;
}
~B() {
cout << "~B(): " << a.i << endl;
}
};
int main(void) {
for(int c = 0; c < 3; ++c) {
A a(c+1);
B b(a);
cout << b.a.i << endl;
}
return 0;
}
Output is:
A(): 1
B(): 1
1
~B(): 1
~A(): 1
~A(): 1
A(): 2
B(): 2
2
~B(): 2
~A(): 2
~A(): 2
A(): 3
B(): 3
3
~B(): 3
~A(): 3
~A(): 3
Compiler is gcc 7.3.0
The destructor is only called once per object. What you don't see in your output is that when you construct b, you create a copy of a using the copy constructor (which in your case is compiler-generated). That doesn't produce any output, but of course the destructor of the copy is also called.
If we add output to the copy constructor, we can see what actually happens:
A(const A& a_) : i(a_.i) {
cout << "A(const A&): " << i << endl;
}
The output shows that each A is copied once, leading to the "duplicated" (not really) destructor calls (live demo). If you want to avoid copying the object, look into C++11's std::move, which is explained in depth elsewhere on this site.
Related
This question already has answers here:
What is The Rule of Three?
(8 answers)
How to actually implement the rule of five?
(6 answers)
Closed 1 year ago.
I am new to code in C++. So, below code looks pretty decent to me.
#include<iostream>
using namespace std;
class B {};
class A {
public:
B* b;
A() {
cout << "ctor" << endl;
b = new B();
}
~A() {
cout << "dtor" << endl;
delete b;
}
};
void func(A a) {
cout << a.b << endl;
}
int main() {
A a;
cout << a.b << endl;
func(a);
cout << a.b << endl;
}
But I found an issue with this code. When I run this, it gives free(): double free detected in tcache 2 error. I know the cause of this error (it is because dtor is being called 2 times).
One way to solve the problem is, writing a copy constructor for class A where, I create a new B() like this.
A(A const& a) {
cout << "copy ctor" << endl;
b = new B();
*b = *a.b;
}
But if B is a very big class, and hence if I prefer to share b pointer, then how would I avoid this problem?
#include <iostream>
using namespace std;
class Base {
public:
~Base() {
static int count = 0;
cout << "...calling destructor " << ++count << endl;
}
};
#include <typeinfo>
int main () {
Base b0;
Base b1 = Base();
Base b2();
cout << typeid(b1).name() << endl;
cout << typeid(b2).name() << " : " << b2 << endl;
return 0;
}
OUTPUT
4Base
F4BasevE : 1
...calling destructor 1
...calling destructor 2
In the above code, I am expecting to create three objects of type Base which has no user-defined constructors.
As is obvious in the output, the destructor is invoked only twice. Inspecting object b2 using typeid, emits the strange F4BasevE string (as opposed to 4Base).
Questions:
What does F4BasevE mean?
1 appear when trying to print b2 - what does that mean?
What constructor do I have to define in the class to be able to
create object b2 the way it is defined?
I wrote a simple program to learn more about the order of creating and destructing objects in C++ (using Visual Studio 2015). Here it is:
#include <iostream>
#include <string>
using namespace std;
class A
{
public:
A(string name)
: name(name)
{
cout << "A(" << name << ")::constructor()" << endl;
}
~A()
{
cout << "A(" << name << ")::destructor()" << endl;
}
private:
string name;
};
class C
{
public:
C(string name, A a)
: name(name), a(a)
{
cout << "C(" << name << ")::constructor()" << endl;
}
~C()
{
cout << "C(" << name << ")::destructor()" << endl;
}
private:
string name;
A a;
};
class B
{
public:
B(string name)
: name(name)
{
cout << "B(" << name << ")::constructor()" << endl;
}
~B()
{
cout << "B(" << name << ")::destructor()" << endl;
}
private:
string name;
A a1{"a1"};
A a2{"a2"};
C c1{"c1", a1};
A a3{"a3"};
};
int main()
{
B b("b1");
return 0;
}
The output surprised me a little bit (the a1s):
A(a1)::constructor()
A(a2)::constructor()
C(c1)::constructor()
A(a1)::destructor()
A(a3)::constructor()
B(b1)::constructor()
B(b1)::destructor()
A(a3)::destructor()
C(c1)::destructor()
A(a1)::destructor()
A(a2)::destructor()
A(a1)::destructor()
To learn more about what was going on I added information about the instances of objects:
A(string name)
: name(name)
{
cout << "A(" << name << ")::constructor(), this = " << this << endl;
}
~A()
{
cout << "A(" << name << ")::destructor(), this = " << this << endl;
}
The result was even more surprising:
A(a1)::constructor(), this = 0039FB28
A(a2)::constructor(), this = 0039FB44
C(c1)::constructor()
A(a1)::destructor(), this = 0039F8A8
A(a3)::constructor(), this = 0039FB98
B(b1)::constructor()
B(b1)::destructor()
A(a3)::destructor(), this = 0039FB98
C(c1)::destructor()
A(a1)::destructor(), this = 0039FB7C
A(a2)::destructor(), this = 0039FB44
A(a1)::destructor(), this = 0039FB28
Namely, why is a1's constructor only called once and destructor 3 times? I'm passing a by value so obviously at least 1 temporary object is created but please explain to me when and how many A instances are created and destroyed?
As already noted in the comments, objects of type A are also constructed via copy-construction when you pass them as arguments by value. In order to see this you can add a copy-constructor on your own:
A(const A& other)
: name(other.name)
{
cout << "A(" << name << ")::copy-constructor(), this = " << this << endl;
}
Sample output:
A(a1)::constructor(), this = 0xbff3512c
A(a2)::constructor(), this = 0xbff35130
A(a1)::copy-constructor(), this = 0xbff350e8
A(a1)::copy-constructor(), this = 0xbff35138
C(c1)::constructor()
A(a1)::destructor(), this = 0xbff350e8
A(a3)::constructor(), this = 0xbff3513c
B(b1)::constructor()
B(b1)::destructor()
A(a3)::destructor(), this = 0xbff3513c
C(c1)::destructor()
A(a1)::destructor(), this = 0xbff35138
A(a2)::destructor(), this = 0xbff35130
A(a1)::destructor(), this = 0xbff3512c
Try it online
As you can see, one copy-construction happens when you pass a1 as parameter to the constructor of c1 and a second one happens when this constructor initializes its member a. The temporary copy is destructed immediately afterwards while the member is destructed when c is destructed.
Edit:
Here you can read the exact rules when a copy-constructor is created.
In order to not create a default copy-constructor it is not sufficient to provide any user-defined constructor, it needs to be a copy/move-constructor.
Edit2:
Taken from C++14 standard (12.8 Copying and moving class objects):
7 If the class definition does not explicitly declare a copy constructor, one is declared implicitly. If the class definition declares a move constructor or move assignment operator, the implicitly declared copy constructor is defined as deleted; otherwise, it is defined as defaulted (8.4). The latter case is deprecated if the class has a user-declared copy assignment operator or a user-declared destructor.
I have 2 simple classes. Base class A, and a derived class B. For debugging purposes, copy constructor and destructor are overridden to cout stuff:
class A
{
protected:
char * c;
public:
A(char * c) : c(c) { cout << "+A " << this << "\n"; }
A(const A& other) : c(other.c) { cout << "*A " << this << "\n"; }
~A() { cout << "-A " << this << "\n"; }
};
class B : public A
{
public:
B(char * c) : A(c) { cout << "+B " << this << "\n"; }
B(const B& other) : A(other.c) { cout << "*B " << this << "\n"; }
~B() { cout << "-B " << this << "\n"; }
};
Here's how I insert an instance of B into the map:
{
cout << "-- 1\n";
map<string, A> m;
cout << "-- 2\n";
m.insert(pair<string, A>( "b", B("bVal")));
cout << "-- 3\n";
}
cout << "-- 4 --\n";
The result of that:
-- 1
-- 2
+A 0051F620
+B 0051F620
*A 0051F5EC
*A 00BD8BAC
-A 0051F5EC
-B 0051F620
-A 0051F620
-- 3
-A 00BD8BAC
-- 4 --
As regards creation of instances, I read this as follows:
B gets created by my own code
that B gets copy-constructed into an A by pair
that last A instance then gets copied once more by the map, where it is inserted
Btw changing the pair in the insert line to a pair<string, B> did not do the trick, it only changed the 2nd step to create an B instead of the A, but the last step would downgrade it to an A again. The only instance in the map that remains until the map itself is eligible for destruction seems to be that last A instance.
What am I doing wrong and how am I supposed to get a derived class into a map? Use maps of the format
map<string, A*>
perhaps?
Update - Solution As my solution is not stated directly in the accepted answer, but rather buried in its comments in form of suggestions, here's what I ended up doing:
map<string, shared_ptr<A>> m;
This assumes your build environment supports shared_ptrs - which mine (C++/CLI, Visual Studio 2010) does.
This is called slicing and happens when a derived class does not fit into the object it has been assigned to. This is the tirivial example:
B b;
A a = b; // This would only copy the A part of B.
If you want to store different types of object in a map, using map<..., A*> is the way to go, as you correctly deduced. Since the object is not stored inside the map but rather somewhere on the heap, there is no need to copy it and it always fits.
map<string, A> m;
will slice the inserted object, which will become an A, losing all other information (it is no longer a B).
Your intuition is correct, you'll need to use pointers or, better yet, smart pointers.
Also, the destructor in A needs to be virtual:
class A
{
//...
virtual ~A() { cout << "-A " << this << "\n"; }
};
otherwise deleting an object of actual type B through a pointer to an A leads to undefined behavior.
For example, in this piece of code, if line [a] is commented out, the output is 0.
inh2.cpp
#include<iostream>
using namespace std;
class A {
public:
int x;
A() { x = 10; }
};
class B : public A {
public:
int x; // <--------- [a]
B() { x = 0; }
};
int main() {
A* ab = new B;
cout << ab->x << endl;
}
results from gcc
$ g++ inh2.cpp
$ ./a.out
10
$
I have two questions:
How does ab->x resolve to 10 in the above case? The object is of type class B, and thus should value to 0.
Why does commenting Line [a] change the behaviour of the code? My reasoning is that x would have anyways been inherited, which should result in same behaviour.
My reasoning for Q #1 above:
ab points to the memory location of an object of class B. It is a physical object in the sense that all the variables with their values are assigned memory.
Variable x within this object stores value 0.
When ab->x is done, ab tells us the memory location of the object, and we go look inside it to find that x is 0. So we should print 0.
Where am I wrong here?
Yes, it is of type B, but you are assigning it as a pointer to an A, and therefore it is using the x defined on A (as when we're dealing with a pointer to A, we don't know that B even exists, even though that's what you allocated).
When you comment out the line, during the construction phase, As constructor is called first, then Bs constructor, which sets x (in its base class) to 0. There is only one x at this point, and Bs constructor is called last.
Making a some small modifications:
#include <iostream>
using namespace std;
class A {
public:
int x;
A()
:x(10)
{
std::cout << __FUNCTION__ << std::endl;
std::cout << x << std::endl;
}
virtual ~A() {}
};
class B : public A {
public:
int x; // <--------- [a]
B()
:A()
,x(0)
{
std::cout << __FUNCTION__ << std::endl;
std::cout << x << std::endl;
}
};
int main() {
A* ab = new B;
cout << "ab->x: " << ab->x << endl;
cout << "ab->A::x " << ab->A::x << endl;
B* b = dynamic_cast<B*>(ab);
cout << "b->x: " << b->x << endl;
cout << "b->A::x " << b->A::x << endl;
cout << "b->B::x " << b->B::x << endl;
}
This gives you:
A
10
B
0
ab->x: 10
ab->A::x 10
b->x: 0
b->A::x 10
b->B::x 0
This demonstrates that:
ab->x refers to A::x because ab is of type A* and there is no such thing as a virtual variable. If you want polymorphism, you'll have to write a virtual int get_x() const method.
B::x hides A::x. This is a bad idea and should be avoided. Consider using a more meaningful name for your member variables and establish whether you can reuse the base class's variable before introducing a new one.
Casting to a B* allows you access to B's members as well as A's. This should be self-explanatory.