I am working on an Entity class which needs to provide access to its data members through setter methods, which check that a value is allowed before storing it (checking code not shown). The Select class is one of the types stored and it needs to do some very specific cleaning up when destroyed:
#include<memory>
#include <iostream>
using namespace std;
class Select {
int i, j;
friend class Entity;
public:
Select(int a, int b) : i{a}, j{b} { }
~Select() {
cout << "destroying " << i << j << endl;
}
protected:
Select() { };
};
class Entity {
Select select_;
public:
void select_setter(const Select &select) {
cout << "to be assigned... " << select.i << select.j << endl;
select_ = select;
}
static shared_ptr<Entity> create(const Select &s) {
auto sp = make_shared<Entity>(Entity{});
sp->select_setter(s);
return sp;
}
};
This block demonstrates how I want the Entity type to be used:
int main() {
auto sp = Entity::create({1, 1});
sp->select_setter({2, 2});
sp->select_setter({3, 3});
cout << "the end" << endl;
return 0;
}
Here is the output:
destroying 00
to be assigned... 11
destroying 11
to be assigned... 22
destroying 22
to be assigned... 33
destroying 33
the end
destroying 33
Why is 33 destroyed twice, but 00, 11 and 22 only once?
When you want to analyse such constructor/destructor behaviour, my recommendation is to:
Log all constructors (default constructor, copy constructor, additional constructor)
Log all destructors
Log address of the objects to know who is who...
By adding more outputs, and implementing Select and Entity copy constructors you can get:
Before auto sp = Entity::create({1, 1});
In Select 1 1 ctor 0x7fff85e31b80 // That's Select temporary object {1,1} being created
In Select default ctor 0x7fff85e31b40 // That's Entity attribute being created before Entity ctor is entered below
In Entity ctor 0x7fff85e31b40 // That's Entity ctor for Entity{} you create
In Select default ctor 0x2248028 // That's Entity attribute being created before Entity ctor is entered below
In Entity copy ctor copy 0x7fff85e31b40 to 0x2248028 // That's copying Entity{} object as the shared_ptr attribute
In Entity dtor 0x7fff85e31b40 // That's Entity{} being destroyed
destroying 00 0x7fff85e31b40 // That's Entity{}'s Select attribute being destroyed. Is 00 but could be anything else (not initialized)
to be assigned... 11
destroying 11 0x7fff85e31b80 // That's Select temporary object {1,1} being destroyed
Before sp->select_setter({2, 2});
In Select 2 2 ctor 0x7fff85e31b70 // That's Select temporary object ({2,2})
to be assigned... 22
destroying 22 0x7fff85e31b70 // That's Select temporary object ({2,2}) being destroyed
Before sp->select_setter({3, 3});
In Select 3 3 ctor 0x7fff85e31b80 // That's Select temporary object ({3,3})
to be assigned... 33
destroying 33 0x7fff85e31b80 // That's Select temporary object ({2,2}) being destroyed
the end
In Entity dtor 0x2248028 // That's Entity stored in the shared_ptr object being destroyed
destroying 33 0x2248028
And this perfectly makes sense...
Simply use this code:
#include <memory>
#include <iostream>
using namespace std;
class Select {
int i, j;
friend class Entity;
public:
Select(int a, int b) : i{a}, j{b} { std::cout << "In Select " << a << " " << b << " ctor" << std::hex << "0x" << this << std::endl;}
~Select() {
cout << "destroying " << i << j << std::hex << "0x" << this << endl;
}
Select( const Select& e ) {
i = e.i; j = e.j;
std::cout << "In Select copy ctor copy " << std::hex << "0x" << &e << " to " << std::hex << "0x" << this << std::endl;
}
Select() { std::cout << "In Select default ctor" << std::hex << "0x" << this << std::endl; }
};
class Entity {
Select select_;
public:
Entity() { std::cout << "In Entity ctor " << std::hex << "0x" << this << std::endl; }
Entity( const Entity& e ) {
select_ = e.select_;
std::cout << "In Entity copy ctor copy " << std::hex << "0x" << &e << " to " << std::hex << "0x" << this << std::endl; }
~Entity() { std::cout << "In Entity dtor " << std::hex << "0x" << this << std::endl; }
void select_setter(const Select &select) {
cout << "to be assigned... " << select.i << select.j << endl;
select_ = select;
}
static shared_ptr<Entity> create(const Select &s) {
auto sp = make_shared<Entity>(Entity{});
sp->select_setter(s);
return sp;
}
};
int main() {
std::cout << "Before auto sp = Entity::create({1, 1});" << std::endl;
auto sp = Entity::create({1, 1});
std::cout << "Before sp->select_setter({2, 2});" << std::endl;
sp->select_setter({2, 2});
std::cout << "Before sp->select_setter({3, 3});" << std::endl;
sp->select_setter({3, 3});
cout << "the end" << endl;
return 0;
}
Related
here is my code snippet:
#include <iostream>
#include <list>
#include <memory>
class A
{
public:
int a = 100;
A()
{
std::cout << "Create A" << std::endl;
}
~A()
{
std::cout << "Release A" << std::endl;
}
virtual void printer() = 0;
};
std::list<std::shared_ptr<A>> arr;
class B : public A
{
public:
int b = 1000;
~B()
{
std::cout << "Release B" << std::endl;
}
void printer() override
{
std::cout << "B's printer" << std::endl;
}
B()
{
std::shared_ptr<A> tmp(this);
arr.push_back(tmp);
(*arr.begin())->printer();
std::cout << "inside B's c'tor test B counts: " << tmp.use_count()
<< std::endl;
}
};
int main(int argc, char const *argv[])
{
std::shared_ptr<B> B_ptr = std::make_shared<B>();
std::shared_ptr<A> A_ptr(B_ptr);
std::cout << "list address: " << (*arr.begin()).get() << std::endl;
std::cout << "test B address: " << B_ptr.get() << std::endl;
std::cout << "test A address: " << A_ptr.get() << std::endl;
std::cout << "list counts: " << (*arr.begin()).use_count() << std::endl;
std::cout << "test B counts: " << B_ptr.use_count() << std::endl;
std::cout << "test A counts: " << A_ptr.use_count() << std::endl;
return 0;
}
My expectation is: A's reference count should be three, but only got 2. I think when I push_back to the list, there should be a temporarily created share_ptr object, even it is get destroyed, the one in list should also pointing to the same address as A_ptr and B_ptr. It turns out that they (those three), did pointing at the same address, but use_count got different results. (*arr.begin()).use_count() is 1, the others are both 2.
Why? Please help.
Ps: I know turning this pointer to share_ptr is stupid operation, but the result doesn't make sense and even disobey the syntax.
My expectation is: A's reference count should be three, but only got 2.
Your expectation is wrong. You only made one copy of the shared pointer, so the use count is 2.
std::shared_ptr<A> tmp(this);
On this line you transfer the ownership of a bare pointer that you don't own into a new shared pointer. Since this was already owned by another shared pointer, the behaviour of the program will be undefined when the two separate owners attempt to destroy it.
Creating a shared pointer from this is possible using std::enable_shared_from_this, but it's not simple.
I've noticed some behaviour which I can't understand in parameterized constructors. Given the following program:
#include <iostream>
using namespace std;
class A {
public:
int x;
A() {}
A(int i) : x(i){
cout << "A\n";
}
~A(){
cout << "dA\n";
}
};
int main(){
A p;
p = 3;
cout << p.x << endl;
p = 5;
cout << p.x << endl;
return 0;
}
I get as output:
A
dA
3
A
dA
5
dA
This means that using = triggers the parameterized constructor, destroys the object on which it's called and creates a new object.
I cannot understand this behaviour and I can't find the answer in the standard ( I am sure it is there somewhere, but it may be stated in a sophisticated way). Could someone help me with an explanation?
The phrase you're probably looking for is "implicit conversion".
If you add a copy constructor and an assignment operator, and then give each object a unique ID, it's easier to see where things go:
int counter = 0;
class A {
public:
int id;
A(): id(++counter) {cout << "A(): " << id << "\n";}
A(int i) : id(++counter) {cout << "A(" << i << "): " << id << "\n";}
// Don't copy the id.
// (This isn't used anywhere, but you can't see that it's not used unless it exists.)
A(const A& a) : id(++counter) {cout << "A(" << a.id << "): " << id << "\n";}
// Don't copy the id here either.
A& operator=(const A&a) {cout << id << " = " << a.id << "\n"; return *this;}
~A(){cout << "destroy: " << id << "\n";}
};
int main(){
A p;
cout << "p is " << p.id << "\n";
p = 3;
cout << "p is " << p.id << "\n";
p = 5;
cout << p.id << "\n";
}
Output:
A(): 1
p is 1
A(3): 2
1 = 2
destroy: 2
p is 1
A(5): 3
1 = 3
destroy: 3
1
destroy: 1
As you can see, the parameterized constructor is used to create a temporary object whose value can be assigned to p, and that temporary is destroyed immediately after that.
You can also see that p is alive and well until the very end.
With a statement like
p = 3;
what you're actually doing is
p = A(3);
which really translates to
p.operator=(A(3));
The temporary A object created by A(3) of course needs to be destructed, it is temporary after all.
The object p itself will not be destructed by the assignment.
When running the following code, it seems that the destructor is running twice. I have a theory that this might have to do with an automatic move constructor being added, but I'm not sure how to test this.
#include <iostream>
#include <functional>
struct Structure {
Structure(int n) :
Value(n) {
std::cout << "constructor: " << Value << std::endl;
}
~Structure() {
std::cout << "destructor: " << Value << std::endl;
}
int Value;
};
int main() {
int Init = 4;
std::function<void()> Function = [Instance = Structure(Init)] () {
std::cout << "Value is: " << Instance.Value << std::endl;
};
Function();
Function();
return 0;
}
Output:
constructor: 4
destructor: 4
Value is: 4
Value is: 4
destructor: 4
Is this output correct?
std::function works by copying the callable object you provide. There is no copy-elision here, since your lambda is not an std::function but of an anonymous, unrelated type.
Hence, the two destructors you see are:
The Instance member of the original, temporary, lambda;
The Instance member of the copy of the lambda that was stored into the std::function and lived until the end of main.
Ok, I defined the move and copy constructors manually and also saw errors when I instructed the compiler to delete them in a variant of the code below. Everything seems normal.
Revised code:
#include <iostream>
#include <functional>
struct Structure {
Structure(int n) :
Value(n) {
std::cout << "constructor: " << Value << std::endl;
}
Structure(const Structure& other) :
Value(other.Value) {
std::cout << "copy constructor: " << Value << std::endl;
}
Structure(Structure&& other) :
Value(other.Value) {
other.Value = -1;
std::cout << "move constructor: " << Value << std::endl;
}
~Structure() {
std::cout << "destructor: " << Value << std::endl;
}
int Value;
};
int main() {
int Init = 4;
std::function<void()> Function = [Instance = Structure(Init)] () {
std::cout << "Value is: " << Instance.Value << std::endl;
};
Function();
Function();
return 0;
}
Output:
constructor: 4
move constructor: 4
destructor: -1
Value is: 4
Value is: 4
destructor: 4
Consider the following class:
struct MyClass {
int mId;
MyClass(int id): mId(id) {}
~MyClass() { std::cout << "deleting: " << mId << std::endl; }
};
And the usage:
std::shared_ptr<MyClass> p(new MyClass(0));
MyClass& m = *p;
m = MyClass(2);
The result is:
deleting: 2
deleting: 2
Please help me understanding:
Why there are two MyClass(2) objects (assumption made on the destructor logs)
Is this a memory leak? Shouldn't the MyClass(0) leak?
Thank you.
There is no memory leak. This code:
m = MyClass(2);
creates a temporary object of type MyClass which is copied into m using the (default-generated) copy assignment operator of MyClass, and then destructed. Eventually, p runs out of scope and its destructor destroys the MyClass instance to which is points (the one bound to m).
If we spelled out all the implicit calls explicitly, this is happening:
// std::shared_ptr<MyClass> p(new MyClass(0));
tmp1.MyClass(0);
p.shared_ptr(&tmp1);
// MyClass& m = *p;
p.operator* ();
// m = MyClass(2);
tmp2.MyClass(2);
m.operator= (tmp2);
tmp2.~MyClass();
// p goes out of scope
p.~shared_ptr();
tmp1.~MyClass();
Do this for a more clear picture of creation destruction:
struct MyClass {
int mId;
MyClass(int id): mId(id) {std::cout << "Creating: " << this << "(" << mId << ")\n";}
~MyClass() {std::cout << "Deleting: " << this << "(" << mId << ")\n";}
MyClass(MyClass const& c)
{std::cout << "Copy: " << this << "(" << mId << ")\n"
" From: " << &c << "(" << c.mId << ")\n";
mId=c.mId;
}
MyClass& operator=(MyClass const& c)
{std::cout << "Assign: " << this << "(" << mId << ")\n"
" From: " << &c << "(" << c.mId << ")\n";
mId=c.mId;
}
};
When I run I get:
Creating: 0x7fc741c000e0(0)
Creating: 0x7fff50ac38c0(2)
Assign: 0x7fc741c000e0(0)
From: 0x7fff50ac38c0(2)
Deleting: 0x7fff50ac38c0(2)
Deleting: 0x7fc741c000e0(2)
There is such code:
#include <iostream>
class A {
public:
int a;
A() : a(0) {
std::cout << "Default constructor" << " " << this << std::endl;
}
A(int a_) : a(a_) {
std::cout << "Constructor with param " << a_ << " " << this << std::endl;
}
A(const A& b) {
a = b.a;
std::cout << "Copy constructor " << b.a << " to " << a << " " << &b << " -> " << this << std::endl;
}
A& operator=(const A& b) {
a=b.a;
std::cout << "Assignment operator " << b.a << " to " << a << " " << &b << " -> " << this << std::endl;
}
~A() {
std::cout << "Destructor for " << a << " " << this << std::endl;
}
void show(){
std::cout << "This is: " << this << std::endl;
}
};
A fun(){
A temp(3);
temp.show();
return temp;
}
int main() {
{
A ob = fun();
ob.show();
}
return 0;
}
Result:
Constructor with param 3 0xbfee79dc
This is: 0xbfee79dc
This is: 0xbfee79dc
Destructor for 3 0xbfee79dc
Object ob is initialized by function fun(). Why copy constructor is not called there? I thought that when function returns by value then copy constructor or assignment operator is called. It seems that object constructed in function fun() is not destroyed after execution of function. How can be copy constructor forced to invoke in this case?
This was compiled by g++.
Why copy constructor is not called there?
RVO
How can be copy constructor forced to invoke in this case?
Pass an option to the compiler. For gcc, it is --no-elide-constructors option to disable the RVO
That is called Named Return Value Optimization and copy elision, and basically means that the compiler has figured out that the copy can be avoided by carefully placing the temporary and the object in the same memory location.
By default there would be three objects in that piece of code, temp inside fun, the return value and ob inside main, and as many as two copies, but by carefully placing temp in the same memory location as the returned object inside fun and placing ob in the same memory address the two copies can be optimized away.
I wrote about those two optimizations with a couple of pictures to explain what is going on here:
NRVO
Copy elision