I'm trying to pass a shared_ptr
to an object around, which may or may not be null:
#include <iostream>
#include <memory>
struct MyObject {
int i = 0;
MyObject(const int i_) : i(i_) {}
};
struct Command {
std::shared_ptr<MyObject> cmdObj;
Command(std::shared_ptr<MyObject>& obj) : cmdObj(obj) {
std::cout << "Store and use this address: " << &obj << std::endl; // [1]
}
void execute() {
if (cmdObj == nullptr) {
cmdObj = std::make_shared<MyObject>(42);
} else {
cmdObj->i = 7;
}
}
};
struct CommandManager {
std::shared_ptr<MyObject> globalObj; // [1]
CommandManager() { globalObj = nullptr; }
void runCommand() {
Command cmd(globalObj);
cmd.execute();
}
};
int main() {
CommandManager cm;
std::cout << "cm.globalObj address: " << &cm.globalObj << std::endl; // [1]
cm.runCommand();
if (cm.globalObj == nullptr) {
std::cout << "globalObj is null" << std::endl;
} else {
std::cout << "globalObj is " << cm.globalObj->i << std::endl;
}
}
As you can see, I'm trying to manipulate or create globalObj
from within Command
. However, despite passing the address in the constructor ([1]
), I'm not storing it correctly so that the new object is usable in the CommandManager
.
How do I make it so I can store and use the address of the shared_ptr<MyObject>
correctly?
Thank you in advance.
CodePudding user response:
To store an address, you need a pointer. In this case, a pointer to a std::shared_ptr.
struct Command {
std::shared_ptr<MyObject>* cmdObj;
Command(std::shared_ptr<MyObject>& obj) : cmdObj(&obj) {
std::cout << "Store and use this address: " << &obj << std::endl; // [1]
}
void execute() {
if (*cmdObj == nullptr) {
*cmdObj = std::make_shared<MyObject>(42);
} else {
(*cmdObj)->i = 7;
}
}
};
You can also use a reference here since you need to pass something to Command
's constructor, and a you won't have a dangling reference problem unless you'd have a dangling pointer problem with the previous option.
struct Command {
std::shared_ptr<MyObject>& cmdObj;
Command(std::shared_ptr<MyObject>& obj) : cmdObj(obj) {
std::cout << "Store and use this address: " << &obj << std::endl; // [1]
}
void execute() {
if (cmdObj == nullptr) {
cmdObj = std::make_shared<MyObject>(42);
} else {
cmdObj->i = 7;
}
}
};
However, I'm not sure you actually want a std::shared_ptr
to begin with. A simple variable would work just as well.
struct Command {
MyObject* cmdObj;
Command(MyObject* obj) : cmdObj(obj) {
std::cout << "Store and use this address: " << obj << std::endl; // [1]
}
void execute() {
cmdObj->i = 7;
}
};
struct CommandManager {
MyObject globalObj = 42; // [1]
void runCommand() {
Command cmd(&globalObj);
cmd.execute();
}
};
int main() {
CommandManager cm;
std::cout << "cm.globalObj address: " << &cm.globalObj << std::endl; // [1]
cm.runCommand();
std::cout << "globalObj is " << cm.globalObj.i << std::endl;
}
If your original intent was to share the MyObject
object through a std::shared_ptr
, then you need to create that object before you can share it.
struct Command {
std::shared_ptr<MyObject> cmdObj;
Command(std::shared_ptr<MyObject>& obj) : cmdObj(obj) {
}
void execute() {
cmdObj->i = 7;
}
};
struct CommandManager {
std::shared_ptr<MyObject> globalObj; // [1]
CommandManager() { globalObj = std::make_shared<MyObject>(42); }
void runCommand() {
Command cmd(globalObj);
cmd.execute();
}
};
Or you can create it in Command
and then share it with CommandManager
.
struct Command {
std::shared_ptr<MyObject> cmdObj;
Command(std::shared_ptr<MyObject>& obj) {
if (obj == nullptr) {
obj = std::make_shared<MyObject>(42);
}
cmdObj = obj;
}
void execute() {
cmdObj->i = 7;
}
};
Or you can create a std::unique_ptr
, share it, and then create the MyObject
object in Command
if needed.
struct Command {
std::shared_ptr<std::unique_ptr<MyObject>> cmdObj;
Command(std::shared_ptr<std::unique_ptr<MyObject>> obj) : cmdObj(obj) {
}
void execute() {
if (*cmdObj == nullptr) {
*cmdObj = std::make_unique<MyObject>(42);
} else {
(*cmdObj)->i = 7;
}
}
};
struct CommandManager {
std::shared_ptr<std::unique_ptr<MyObject>> globalObj; // [1]
CommandManager() { globalObj = std::make_shared<std::unique_ptr<MyObject>>(nullptr); }
void runCommand() {
Command cmd(globalObj);
cmd.execute();
}
};
CodePudding user response:
Note: a reference is not an address, despite using the same &
character that is used for the address-of operator. I assume that you are talking about references, not addresses.
You pass a globalObj
reference to the constructor, however the only reference there is the formal parameter obj
.
You print the address of obj
and verify that it is the same as the address of globalObj
, which is expected. But cmdObj
is not obj
.
cmdObj
is an object, not a reference. Initialising it will create a copy of whatever you initialising it from, completely unlinked from the original. Its address is different, and you can print it and see for yourself.
Changing cmdObj
(the copy) will not affect globalObj
(the original) in any way, shape, or form.
if (cmdObj == nullptr) {
// Before the assignment, globalObj was nullptr.
cmdObj = std::make_shared<MyObject>(42);
// And it still is after the assignment to cmdObj.
You can make cmdObj
a reference too:
Mystd::shared_ptr<MyObject>Object*& cmdObj;
and this will likely have the effect you want.
Note, I am not talking about the object(s) pointed to by any of these smart pointers. Just about the pointers themselves.
CodePudding user response:
Minimal program which displays the difference between shared_ptr
lifetime and lifetime of the object they point to.
#include <iostream>
#include <memory>
int main() {
// Create an integer, give shared ownership to sp_1
std::shared_ptr<int> sp_1 = std::make_shared<int>(2);
// sp_2 points to same integer now, so sp_2.get() == sp_1.get().
// Reference count is increased to 2.
// Note that sp_1 and sp_2 are still different objects, i.e. &sp_2 != &sp_1
std::shared_ptr<int> sp_2 = sp_1;
// Value of int is changed (both sp_1 and sp_2 point to it)
*sp_2 = 4;
// Replace shared_ptr object sp_2. It now points to another integer object.
// sp_1 is not affected by it, the reference count to the first integer is back down to 1.
sp_2 = std::make_shared<int>(6);
std::cout << *sp_1 << " is still 4!" << std::endl;
std::cout << *sp_2 << " is 6!" << std::endl;
// "Destroy" first integer object
sp_1.reset();
// This is perfectly valid since sp_2 is pointing to a different integer
*sp_2 = 8;
std::cout << *sp_2 << " is 8!" << std::endl;
return 0;
}
Run it here: https://godbolt.org/z/qMKvzzbWE