Something about shared_ptr I don't get - c++

When I run this code:
#include <iostream>
#include <memory>
struct Adaptee {
int value = 0;
};
class Adapter {
private:
Adaptee* adaptee;
public:
Adapter (Adaptee* a) : adaptee(a) {}
void increaseValueByOne() {adaptee->value++;}
};
int main() {
Adaptee* a = new Adaptee;
Adapter* adapter = new Adapter(a);
adapter->increaseValueByOne();
delete adapter;
std::cout << "a->value = " << a->value << std::endl;
}
I get the output: a->value = 1. But when I run this:
struct Adaptee {
int value = 0;
};
class Adapter {
private:
std::shared_ptr<Adaptee> adaptee;
public:
Adapter (Adaptee* a) : adaptee (std::shared_ptr<Adaptee>(a)) {}
void increaseValueByOne() {adaptee->value++;}
};
int main() {
Adaptee* a = new Adaptee;
Adapter* adapter = new Adapter(a);
adapter->increaseValueByOne();
delete adapter;
std::cout << "a->value = " << a->value << std::endl;
}
I get: a->value = 7738248
Why is this? Isn't the use_count of adapter->adaptee simply changing from 2 to 1, so nothing should go wrong? When I remove the line 'delete adapter;' everything is fine though, but then there is leaking.

Because in the second case Adaptee instance is deleted by the destructor of shared_ptr when you delete adapter as it's the last shared_ptr owning that object. See the doc.
If you want to keep the adaptee alive after you delete adapter, you should wrap it into shared_ptr from the start:
class Adapter {
private:
std::shared_ptr<Adaptee> adaptee;
public:
Adapter (const std::shared_ptr<Adaptee>& a) : adaptee (a) {}
void increaseValueByOne() {adaptee->value++;}
};
int main() {
std::shared_ptr<Adaptee> a(new Adaptee);
Adapter* adapter = new Adapter(a);
adapter->increaseValueByOne();
delete adapter;
std::cout << "a->value = " << a->value << std::endl;
}

In the first case, the line
delete adapter;
did nothing to adaptee. It is still a valid pointer.
In the second case, the same line calls the destructor of the shared_ptr, which deallocates the memory pointed to by adaptee. Hence, adaptee is not a valid pointer and you can expect undefined behavior if you dereference it.

The principle of the shared_ptr is to manage the deletion of the pointed object when it's no longer used.
When you create the shared_ptrin your adapter, it takes ownership of the object and sets the use count to 1. When the adapter is deleted, so is the shared pointer. THe use count (that you can display with use_count()) is hence automatically decremented and reaches zero. So the object is deleted and a points to an invalid address.
If you dont' want the shared_ptr to release the memory of a when deleting adapter, you should create your original pointer as shared_pointer as well:
int main() {
auto a = std::make_shared<Adaptee>(); //shared pointer instead of Adaptee* a = new Adaptee;
...
}
And add an additional constructor in class Adapter:
Adapter(std::shared_ptr<Adaptee> a) : adaptee(a) {}
With this consistent use of shared_ptr,the use count of shared pointer would go to 1 when adapter is deleted, and the rest of your code will work as you exepect, printing 1 as in your original version.

To expand on what others are saying, when you give a raw heap allocated pointer to a shared_ptr, you are giving the shared_ptr control over the lifetime of the resource. shared_ptr keeps a reference count of how many shared_ptrs are pointing to a resource. When that count is 0 the resource is cleaned up.
adaptee (std::shared_ptr<Adaptee>(a))
Here, the reference count is 1 because there is 1 shared_ptr pointing to the resource a.
delete adapter;
causes the shared_ptr in adapter to go out of scope. The shared_ptr's destructor is called, which decrements the reference count. Since the reference count is now 0, delete is called on a.
std::cout << "a->value = " << a->value << std::endl;
Here, a is no longer a valid pointer because delete was called in the destructor of the shared_ptr.
If you want a to stay valid you should do the following:
struct Adaptee {
int value = 0;
};
class Adapter {
private:
std::shared_ptr<Adaptee> adaptee;
public:
Adapter (std::shared_ptr<Adaptee> a) : adaptee(a) {}
void increaseValueByOne() {adaptee->value++;}
};
int main() {
std::shared_prt<Adaptee> a = std::make_shared<Adaptee>(); //reference count is 1
Adapter* adapter = new Adapter(a); //reference count is 2
adapter->increaseValueByOne();
delete adapter; //reference count is 1
std::cout << "a->value = " << a->value << std::endl;
} // reference count is 0; memory is deallocated

Related

How does a shared_ptr handle copy to a pure virtual base class?

Class B expects to receive an instance of shared_ptr<IError>.
Class A implements IError and is passed by value to the constructor of B.
I would like to understand how this scenario is handled. How does the shared_ptr as a template class handle the conversion to IError?
In a simple case where B receives shared_ptr<A> I assume the copy constructor is called and the reference counter is increased. However since IError is pure virtual a normal copy constructor invocation seems not to be case here?
// Example program
#include <iostream>
#include <string>
class IError
{
public:
virtual ~IError(){};
virtual void OnError() = 0;
};
class A : public IError
{
public:
A(){};
void OnError(){std::cout << "Error Occured" << std::endl;}
};
class B
{
public:
B(std::shared_ptr<IError> errorReporter): mErrorReporter(errorReporter){}
void Action(){mErrorReporter->OnError();}
private:
std::shared_ptr<IError> mErrorReporter;
};
int main()
{
auto objA = std::make_shared<A>();
auto objB = std::make_shared<B>(objA);
objB->Action();
}
Debugging time! Let's find out what happens by using the tools we have available as developers.
The memory of the shared_ptr objA looks like this (type &objA in the memory window; it will be replaced by its address):
It has a pointer to the object (000002172badd8e0) and a pointer to the control block.
The control block looks like this (copy and paste the second value into a new memory window):
It has a pointer to the allocator (first 2 columns), the reference count (1) and the weak reference count (0 + offset 1).
After objB has been created, the control block of objA has changed to a reference count of 2:
And the shared_ptr objB looks like this:
It points to the a shared pointer and to the control block.
The shared pointer in objB points to the same object as before (000002172badd8e0), so no copy of the actual object has been made.
The control block of objB indicates that objB only has a reference count of 1:
a normal copy constructor invocation seems not to be case here?
Correct. No copy of the object is made, as we can confirm with a debugger. But a copy of the shared_ptr has been made.
It doesn't.
Copy a shared_ptr doesn't copy it's point-to object, just like normal pointer
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<IError> i = a;
A* a = new A;
IError* i = a; // no copy A
You are right in that the base class IError is abstract, hence it cannot be instantiated, never mind copied.
The code below has been modified from the original to show how each newly created shared_ptr just increments the reference count of the original shared_ptr. So, a shallow copy.
In your code, as well as in the code below, the underlying object to these shared_ptrs is concrete class A, derived from the abstract IError, so it is legal to shallow copy it.
// Example program
#include <iostream>
#include <string>
#include <memory>
class IError
{
public:
virtual ~IError(){};
virtual void OnError() = 0;
};
class A : public IError
{
public:
A(){std::cout << "A created.\n";};
void OnError(){std::cout << "Error Occured" << std::endl;}
};
class B
{
public:
B(std::shared_ptr<IError> errorReporter): mErrorReporter(errorReporter){
std::cout << "B created from A.\n";
}
void Action(){mErrorReporter->OnError();}
private:
std::shared_ptr<IError> mErrorReporter;
};
int main()
{
auto objA = std::make_shared<A>();
std::cout << "I. Reference count for objA: " << objA.use_count() << '\n';
auto objB = std::make_shared<B>(objA);
std::cout << "II. Reference count for objA: " << objA.use_count() << '\n';
// objB->Action();
auto objBB = std::make_shared<B>(*objB);
std::cout << "Created objBB from objB\n";
std::cout << "III. Reference count for objA: " << objA.use_count() << '\n';
std::cout << "Reference count for objB: " << objB.use_count() << '\n';
std::cout << "Reference count for objBB: " << objBB.use_count() << '\n';
// auto objB_from_base = std::make_shared<B>(IError()); // ERROR: IError is an abstract class
}
with output:
A created.
I. Reference count for objA: 1
B created from A.
II. Reference count for objA: 2
Created objBB from objB
III. Reference count for objA: 3
Reference count for objB: 1
Reference count for objBB: 1

C++ - shared_ptr of abstract class

I have the following code:
#include <string>
#include <queue>
#include <thread>
#include <iostream>
using namespace std;
class MsgType {
public:
virtual string getData() const = 0;
static MsgType* getMsg();
};
class Msg1 : public MsgType {
string getData() const override final {
return "Msg1";
}
};
class Msg2 : public MsgType {
string getData() const override final {
return "Msg2";
}
};
queue<shared_ptr<MsgType>> allMsgs;
MsgType* MsgType::getMsg() {
shared_ptr<MsgType> msg_sp = nullptr;
if (!allMsgs.empty()) {
msg_sp = allMsgs.front();
allMsgs.pop();
}
if (msg_sp) {
MsgType* mt = msg_sp.get();
cout << "[in the method] " << mt->getData() << endl;
return mt;
} else {
return nullptr;
}
}
int main() {
MsgType* msg1 = new Msg1();
MsgType* msg2 = new Msg2();
shared_ptr<MsgType> msg;
msg.reset(msg1);
allMsgs.push(msg);
msg.reset(msg2);
allMsgs.push(msg);
MsgType* tryGetMsg = MsgType::getMsg();
cout << "[out of the method] " << tryGetMsg->getData() << endl;
}
In the MsgType::getMsg() method I can see the output, but in the main() I can't. I belive that it's trying to call MsgType::getData() which is virtual.
How can I get the MsgType outside of this method, in a way that I can access the derived class' methods?
Thanks!
The immediate fix is to just return a shared_ptr from getMsg:
shared_ptr<MsgType> MsgType::getMsg() {
shared_ptr<MsgType> msg_sp;
if (!allMsgs.empty()) {
msg_sp = allMsgs.front();
allMsgs.pop();
}
if (msg_sp) {
cout << "[in the method] " << msg_sp->getData() << endl;
}
return msg_sp;
}
and stop converting needlessly between smart and raw pointers.
The message object must be kept alive until the caller has finished using it. Since you're using shared_ptr to manage the object lifetime, you need a shared_ptr to continue existing as long as you want to use the object.
In general, mixing raw and smart pointers to the same objects is risky, because the smart pointers can only track the references they know about: that is, shared_ptr has to know everywhere a pointer to the object is being shared. It can only do this if every one of those pointers is a shared_ptr.
Note also that the easy way to diagnose object lifetime problems is to write a destructor that logs something. This brings us on to the second problem: in order for MsgType to be a suitable abstract base class here, it needs a virtual destructor.
Without that, the shared_ptr will try to destroy your object when the refcount becomes zero, but be unable (in general) to do so correctly.
class MsgType {
public:
virtual ~MsgType() {}
virtual string getData() const = 0;
};
Veering finally into code review, I intentionally omitted getMsg above.
Having a class static method to access a global queue is just weird. If you want to keep that layout, the allMsgs queue should probably be class static as well.
Instead, it's probably better to just keep a msg_queue object wherever you actually need it, with no statics or globals.
Here:
MsgType* MsgType::getMsg() {
shared_ptr<MsgType> msg_sp = nullptr;
if (allMsgs.empty()) {
msg_sp = allMsgs.front();
allMsgs.pop();
}
if (msg_sp) {
MsgType* mt = msg_sp.get();
cout << "[in the method] " << mt->getData() << endl;
return mt;
} else {
return nullptr;
}
}
When allMsgs is not empty you you copy front then pop. At that moment there is a single shared_ptr managing that object: msg_sp. Then you retrieve a raw pointer via get and return that, but when the function returns the use-count decrements to 0 and the managed object is destroyed. The returned pointer is invalid.
I find your mixing of raw and shared_ptr a little confusing. When you have a shared_ptr managing the lifetime of the object then you cannot first get a raw pointer, then let the shared_ptr destroy the managed object and still use the raw pointer. You need to properly transfer ownership when you don't want the shared pointer to destroy the managed object.
I don't know why you are mixing std::shared_ptr and C-style pointers this way, but let's ignore this and assume it's just as an exercise.
Having a look at the bottom half of your code (slightly reduced), we have this:
std::queue<std::shared_ptr<MsgType>> allMsgs;
MsgType* MsgType::getMsg();
int main() {
MsgType* msg1 = new Msg1();
std::shared_ptr<MsgType> msg;
msg.reset(msg1); // <--- 1. here, msg1 is owned by msg
allMsgs.push(msg); // <--- 2. now, msg1 is also owned by allMsgs
msg.reset(); // <--- 3. msg1 only owned by allMsgs
MsgType* tryGetMsg = MsgType::getMsg(); // <--- see below : nobody keeping msg1 alive!
std::cout << "[out of the method] " << tryGetMsg->getData() << std::endl;
}
MsgType* MsgType::getMsg() {
std::shared_ptr<MsgType> msg_sp = nullptr;
if (!allMsgs.empty()) {
msg_sp = allMsgs.front(); // <--- 4. msg1 owned by msg_sp & allMsgs
allMsgs.pop(); // <--- 5. msg1 owned by msg_sp only
}
if (msg_sp) {
MsgType* mt = msg_sp.get();
std::cout << "[in the method] " << mt->getData() << std::endl;
return mt;
} else {
return nullptr;
}
} // <--- 6. msg_sp destroyed... oh oh... msg1 dead :)
As as small addition, you can construct a shared base class pointer directly from a derived one, e.g.
auto msg_sp = std::shared_ptr<MsgType>(std::make_shared<Msg1>());

How to fix this shared_ptr reference cycles?

I designed an App that holds a stack of layers and an active obj.
When a Layer is attached to the App, the Layer tells App what an active object is. But my design causes a sigtrap when deallocating.
It cause sigtrap because the destruction of shared_ptr<Obj> m_obj in App happens first which reduce the use_count to 1. Then the onDetech function gets call, setting the active shared_ptr<Obj> m_obj to nullptr and reduce use_count to 0! But the layer still holds an shared_ptr<Obj>.
The code below is a minimal example to reproduce. I notice my code has a reference cycle but I have no idea how to fix this except using a raw pointer for obj in the App class.
I used shared_ptr for obj in App because it makes sense to me that App has the shared ownership of obj. Should I not use shared_ptr in this case?
class App;
class Obj {
public:
~Obj() { std::cout << "obj destruct" << std::endl; }
};
class Layer {
public:
Layer() { m_obj = std::make_shared<Obj>(); }
~Layer() { std::cout << m_obj.use_count() << std::endl; }
void onAttach(App *app);
void onDetach();
std::shared_ptr<Obj> m_obj;
private:
App *m_app;
};
class LayerStack {
public:
void pushLayer(App *app, std::shared_ptr<Layer> layer) {
m_layers.push_back(layer);
layer->onAttach(app);
}
~LayerStack() {
for (auto &layer : m_layers) {
layer->onDetach();
}
}
private:
std::vector<std::shared_ptr<Layer>> m_layers;
};
class App {
public:
App() {
m_defaultLayer = std::make_shared<Layer>();
m_stack.pushLayer(this, m_defaultLayer);
}
~App() {}
LayerStack m_stack;
std::shared_ptr<Layer> m_defaultLayer;
std::shared_ptr<Obj> m_activeObj;
};
void Layer::onAttach(App *app) {
m_app = app;
app->m_activeObj = m_obj;
std::cout << m_obj.use_count() << std::endl;
}
void Layer::onDetach() {
m_app->m_activeObj = nullptr;
std::cout << m_obj.use_count() << std::endl;
}
int main() {
A a;
}
output:
2
obj destruct
-923414512
-923414512
You're accessing m_activeObj after its lifetime has ended, and thus the behavior of your program is undefined.
The sequence of events is as follows:
App object goes out of scope
~App runs
m_activeObj is destroyed; after this its lifetime has ended and it can no longer be accessed
m_defaultLayer is destroyed
m_stack is destroyed
m_layers[0].onDetach() is called
onDetach sets m_app->m_activeObj to nullptr, but its lifetime has already ended, so behavior is undefined.
Irrelevant other stuff; you're already screwed.
The solution is to reorder things so that you don't access m_activeObj after its lifetime has ended. Either move m_stack's declaration after m_activeObj so it gets destroyed first or clear it manually in ~App.

Does the non default destructor be called when a pointer goes out of reference in cpp 17?

```
Class A {
....
....
..
/// option A
~A() = default
/// option B
~A() {
///destructor code here
}
}
```
Suppose I go with option B where I have defined my own deconstructor, when a pointer pointing to an object holding the above class, I do something like
objAPtr = nullptr;
is the destructor called? or does the above only work with option B
I am using smart pointers here:
objAPtr = make_shared<A>();
If you have e.g.
A* objAPtr = new A;
objAPtr = nullptr;
then no, the object pointed to by objAPtr will not be destructed. Instead you will lose your only reference to the object, and it will be a leak.
And this has nothing to do with what "kind" of destructor you have.
No, the destructor is not called automatically by setting a pointer to an object to nullptr. When and how the destructor is called is not only orthogonal to whether you = default-declare it, it's determined by the circumstances and how the memory for holding your instance of A was allocated. Example with an A instance on the stack:
{
A aInstance;
A *somePointerToA = &aInstance;
somePointerToA = nullptr; // aInstance not touched, only the pointer to it.
} // aInstance goes out of scope, now the destructor is called.
And when created on the heap:
auto *aInstance = new A;
a *somePointerToA = aInstance;
somePointerToA = nullptr; // aInstance again untouched.
delete aInstance; // Now the destructor is called.
These semantics change when managing your instance with a smart pointer, that takes care of the destruction. Example:
#include <memory>
auto aInstance = std::make_unique<A>();
aInstance = nullptr; // The std::unique_ptr calles A's destructor
auto aSharedInstance = std::make_shared<A>();
auto anotherSharedInstance = aSharedInstance;
aSharedInstance = nullptr; // dtor not called because of anotherSharedInstance
anotherSharedInstance = nullptr; // last pointer to the instance, dtor is called
Yes the destructor is called on setting the variable to nullptr or doing a reset too:
class Test
{
private:
int m_nID;
public:
Test(int nID)
{
std::cout << "Constructing Test " << nID << '\n';
m_nID = nID;
}
~Test()
{
std::cout << "Destructing Test " << m_nID << '\n';
}
int getID() { return m_nID; }
};
int main()
{
// create a shared pointer
auto pTest = std::make_shared<Test>(1);
std::cout << pTest->getID() << '\n';
// Allocate a Test dynamically
auto pTest2 = std::make_shared<Test>(2);
std::cout << pTest2->getID() << '\n';
pTest = nullptr;
//pTest.reset();
std::cout << "before sleep \n ";
sleep(5);
return 0;
} // Test goes out of scope here
Running the above the destructor is called on setting it to nullptr.
compile options
g++ def_destr.cpp -std=c++14

Automatically adding and removing an object from a list

I have a class. When this class is instantiated, I want the instance added to a list. When the object is deleted, I want it removed from the list.
So I give the object a shared pointer to itself. I then have a list of weak pointers to those shared pointers. When an object is created, it creates a shared pointer to itself, makes a weak pointer to that, and puts the weak pointer in a list.
When the object is destroyed, the shared pointer is as well. Whenever I try to access a member in the list, I ensure that it hasn't expired and that its use count isn't 0. Despite this, I still crash when the list member is destroyed. Why? Can I get around it? Here's my SSCCE:
#include <iostream>
#include <memory>
#include <vector>
class test
{
private:
std::shared_ptr<test> self;
public:
int val;
test(int set);
test(test &copy) = delete; // making sure there weren't issues
// with a wrong instance being deleted
};
std::vector<std::weak_ptr<test>> tests;
test::test(int set):
val(set)
{
this->self = std::shared_ptr<test>(this);
tests.push_back(std::weak_ptr<test>(this->self));
}
void printTests()
{
for (auto i = tests.begin(); i != tests.end(); i++)
{
if (i->use_count() == 0 || i->expired())
{
tests.erase(i);
continue;
}
std::cout << i->lock()->val << std::endl;
}
std::cout << std::endl;
}
int main(int argc, char **argv)
{
{
test t(3);
std::cout << "First tests printing: " << std::endl;
printTests();
} // SEGFAULTS HERE
std::cout << "Second tests printing: " << std::endl;
printTests();
return 0;
}
The output of this program is as follows:
First tests printing:
3
Segmentation fault (core dumped)
Your issue is with how you are creating the self pointer:
this->self = std::shared_ptr<test>(this);
When a shared_ptr is created with this constructor, according to the documentation,
When T is not an array type, constructs a shared_ptr that owns the pointer p.
...
p must be a pointer to an object that was allocated via a C++ new expression or be 0
So the issue is that the shared_ptr is taking ownership of your stack object, so when the object gets destructed (and the shared_ptr along with it), shared_ptr is trying to delete your object that is on the stack. This is not valid.
For your use case, if you expect tests to outlive your vector, then you might be able to just store this.
I think the OP is interested in a solution to his original problem even if it uses a different method than the one he attempted. Here is a simple example of how to add an object to a global list when it is constructed, and remove it when it is deleted. One thing to remember: you must call AddList in every constructor you add to your base class. I didn't know whether you want the list to be accessible outside the class or not, so I added getter functions to return non-const iterators to the list.
class MyClass
{
private:
static std::list<MyClass*> mylist;
std::list<MyClass*>::iterator mylink;
// disable copy constructor and assignment operator
MyClass(const MyClass& other);
MyClass& operator = (const MyClass& other);
void AddList()
{
mylink = mylist.insert(mylist.end(), this);
}
void RemoveList()
{
mylist.erase(mylink);
}
public:
MyClass()
{
AddList();
}
virtual ~MyClass()
{
RemoveList();
}
static std::list<MyClass*>::iterator GetAllObjects_Begin()
{
return mylist.begin();
}
static std::list<MyClass*>::iterator GetAllObjects_End()
{
return mylist.end();
}
virtual std::string ToString() const
{
return "MyClass";
}
};
class Derived : public MyClass
{
virtual std::string ToString() const
{
return "Derived";
}
};
std::list<MyClass*> MyClass::mylist;
int main()
{
std::vector<MyClass*> objects;
objects.push_back(new MyClass);
objects.push_back(new MyClass);
objects.push_back(new Derived);
objects.push_back(new MyClass);
for (std::list<MyClass*>::const_iterator it = MyClass::GetAllObjects_Begin(), end_it = MyClass::GetAllObjects_End(); it != end_it; ++it)
{
const MyClass& obj = **it;
std::cout << obj.ToString() << "\n";
}
while (! objects.empty())
{
delete objects.back();
objects.pop_back();
}
}
This line is trouble:
tests.erase(i);
An iterator pointing to the erased element is invalid, and you can't increment it any longer. Luckily, erase returns a new iterator you can use:
auto i = tests.begin();
while (i != tests.end())
{
if (i->use_count() == 0 || i->expired())
{
i = tests.erase(i);
}
else {
std::cout << i->lock()->val << std::endl;
++i;
}
}