简体   繁体   中英

Is C++ shared_ptr::operator* dangerous?

  shared_ptr<int> ptr = make_shared<int>(10);
  int& val = *ptr;
  ptr = make_shared<int>(20);
  // val now points to freed memory

In the above code, I can read from and write to val which is pointing to freed memory. Same issue applies if we use .get() in shared_ptr. So, it is possible to shoot ourselves in the foot even if we resort to using smart pointers.

Nobody will code like above obviously. One way to hit this is if we have something like this -

class Foo {
public:
   int& getVal() { return *p; }
private:
   shared_ptr<int> p;
};

Someone can call getVal() after which some other member of the above class can choose to overwrite p with a different value. If getVal() above returns a shared_ptr instead of a reference, we will not see this issue. Some folks might argue that returning a shared_ptr is more expensive than returning reference since we need to increment the counter in the shared_ptr control block.

So, should the guideline be to not return a reference to a shared_ptr like above?

It isn't operator* that is the danger here, it is storing references.

Storing references to anything means you are personally responsible for the lifetime of the thing you are referring to.

int getVal() { return *p; }

is the safest, simplest solution. C++ adores values.

Have a more complex object that is expensive to copy? Then take greater care.

gsl::span<int const> getVals() { return {p->data(), p->size()}; }

here we have something that is conceptually a reference despite not being of reference or pointer type.

Consumers of it have to be aware of the lifetime rule of what getVals returns. If they want to persist it, they are responsible for copying the data out.

Only in extreme situations should you consider:

std::shared_ptr<gsl::span<int const>> getVals() {
  return p?make_shared_span<int const>( p, {p->data(), p->size()} ):nullptr;
}

where make_shared_span creates a shared span-view into a shared ptr.

Sharing references to mutable data is expecially scary. You never know if the data is going to change under your feet, which makes the state of any code working with it entangle with the state of all of the code that also has a reference to it.


One way C++ code bases get around the "sharing references" and lifetime issue is to work with immutable data.

A shared_ptr<gsl::span<int const>const> that is actually immutable is a far more sane thing to work with than a shared_ptr<gsl::span<int>> .

You'll see this in designs, like here or backing a document with immutable libgit store .


"Solving" this by passing shared pointers to data all around will result in objects living far longer than they should, as people store some shared pointer to them without ever intending to use it again.

Pay attention to lifetime, and you get wonderful RAII results.

There are situations where sharing a shared pointer around make sense, and that is when you actually want shared ownership , not when "I don't want to think about lifetime". Shared ownership is a more complex kind of lifetime, using it because in some situations you don't have to think about lifetime results in lifetime-spaghetti.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM