Home > OS >  Storing and using smart_ptr address
Storing and using smart_ptr address

Time:11-23

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;
    }
  }
};

Demo

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;
    }
  }
};

Demo

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;
}

Demo

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();
  }
};

Demo

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;
  }
};

Demo

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();
  }
};

Demo

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

  • Related