4

Suppose I had a Manager Class that held a vector of some object:

class SomeObjectManager
{
private:
    std::vector<SomeObject> _heldObjects;
};

And in that class I had some function that iterated through said vector to return the requested object.

SomeObject getSomeObjectByName(std::string nameToFind);

What I need to know is when is it proper to use smart pointers. Should I actually be returning something like below?

std::shared_ptr<SomeObject> getSomeObjectByName(std::string nameToFind);

Or should I be using something else like unique_ptr or weak_ptr? I want the SomeObjectManager class to own the actual object being returned and never have said SomeObject be deleted unless the Manager makes it so.

I have only recently came back to the C++ world after being in C# mode for quite some time; thanks for the help and clearing up my confusion.

I have read a lot about this matter but never really found a straight answer to my particular situation.


Edit #1

I'd like to reword my last few sentences with this:

I want the SomeObjectManager class to own the actual object being returned and never have said SomeObject be removed from the vector and subsquently deleted, fall out of scope, until the Manager forces it to do so. For example:

void SomeObjectManager::removeSomeObjectByName(const std::string& objectToRemove);

This would just iterate over the vector, finding said SomeObject, and remove it from the Vector.

Mister
  • 487
  • 3
  • 17

6 Answers6

6

Since SomeObjectManager is the owner of the SomeObject instances (stored in its std::vector data member), I'd just return raw pointers, since they are actually observing pointers.

std::vector<SomeObject> _heldObjects;

SomeObject* getSomeObjectByName(const std::string& nameToFind) {
    ... find index of object corresponding to 'nameToFind'
    return &_heldObjects[foundIndex];
}

(Note that I passed nameToFind using reference to const, since I assume that nameToFind is an input string, so if inside the method you are just observing that string, you can avoid deep-copies using const &).

You must pay attention when you have owning raw pointers (they should be wrapped inside safe RAII boundaries), but observing raw pointers are fine.

Just make sure that the lifetime of SomeObjectManager exceeds that of its clients, to make sure that the clients are referencing valid objects.

Note also that if you add new items to the vector data member (e.g. using std::vector::push_back()), the addresses of the previous SomeObject instances stored in the vector can change. So, if you gave pointers to those outside, they become invalid.

So, make sure that the vector size and vector content are not changed before you give pointers to its elements to client code outside.

An alternative would be to have std::vector<std::unique_ptr<SomeObject>> as data member. In this case, even if the vector is resized, the addresses you returned using the smart pointers (in particular using std::unique_ptr::get()) are still valid:

std::vector<std::unique_ptr<SomeObject>> _heldObjects;

SomeObject* getSomeObjectByName(const std::string& nameToFind) {
    ... find index of object corresponding to 'nameToFind'
    return _heldObjects[foundIndex].get();
}

PS
Another option might be returning references to const SomeObject (assuming that this use of const makes sense in your design):

std::vector<SomeObject> _heldObjects;

const SomeObject& getSomeObjectByName(const std::string& nameToFind) const {
    ... find index of object corresponding to 'nameToFind'
    return _heldObjects[foundIndex];
}
Mr.C64
  • 41,637
  • 14
  • 86
  • 162
  • Alright, I need to read more on `const` but I will ask it anyway; wouldn't returning a `const SomeObject` make that returned object immutable? Preventing modification of any/all aspects of the object? For instance; `someObjInst.setTitle("something new")`. Please correct me if I am wrong. – Mister Apr 15 '14 at 17:36
  • @Mister: The problem with returning by value (`const SomeObject`, and `SomeObject` would be similar) is that this will make a *copy* (deep copy) of the `SomeObject` instance you returned. Instead I think you want to give the caller a _reference_ to the **original** `SomeObject` instance stored in the `std::vector` data member. For this, you can use _pointers_ or _references_. – Mr.C64 Apr 15 '14 at 17:39
  • Alright that makes sense, one last question though isn't `const SomeObject&` a reference to a `const SomeObject`? With my needs I would want just `SomeObject&` would I not? – Mister Apr 15 '14 at 17:54
  • When you return `const SomeObject&`, you are returning a reference, but the object referenced to can't be modified at the call site. If you pass `SomeObject&`, you are aliasing the original `SomeObject` instance in the `std::vector` data member, so it could be even reassigned to some other value. I don't know your design, but is this something you really want? – Mr.C64 Apr 15 '14 at 17:56
  • No that isn't a desired effect I would want but I thought `SomeObject&` would also return a reference; am I wrong in thinking that? Also, I thought references can't be reassigned. – Mister Apr 15 '14 at 18:04
  • You are right `SomeObject&` returns (is) a reference. And with these non-const references you can reassign the original item: [see it live](http://ideone.com/bYxG6p). – Mr.C64 Apr 15 '14 at 18:23
  • Ahhh ok I see it now. Alright so this points to me learning more about `const` -- thats fine. I appreciate the help and I feel like you have answered my question the best way I needed it answered. I really appreciate it! – Mister Apr 15 '14 at 18:51
4

If your program runs in a single thread, you are mostly good with returning raw pointers or references to the objects that are stored in the vector, if you have sufficient discipline.

Since the manager privately owns the vector and the objects inside, and thus controls when objects are deleted, you can make sure that there remain no invalid pointers to objects that have been deleted (this isn't automatically guaranteed!).
Basically, the manager must only ever delete objects when it knows that nobody holds a reference to that object, for example by only doing this at distinct, well-defined times (such as at program end, or when it knows that no consumers remain, or such).
Reference counting is one way of doing that, and it is what shared_ptr does internally, too (well, no... strictly to the letter of the specification, reference counting isn't required, only the visible behavior is defined, but practially all implementations do it).

The process of "deleting" an object would thus merely decrement the reference counter (much like in a managed language) and the object would really cease to exist when the reference counter reaches zero. Which is probably but not necessarily immediately the case when you "delete" it. It might as well take a few moments before the object is actually destroyed.
That approach works "automatically" without a lot of diligence and rigid discipline, and it can be implemented simply by storing shared_ptrs of your objects in the vector and returning either shared_ptrs or weak_ptrs.

Wait a moment! Then why are there even weak_ptrs if you can just return a shared_ptr too? They do different things, both logically and practically. shared_ptrs own (at least partially), and weak_ptrs do not. Also, weak_ptrs are cheaper to copy.

In a multi-threaded program, storing shared_ptrs and returning a weak_ptr is the only safe approach. The weak_ptr does not own the resource and thus cannot prevent the manager from deleting the object, but it gives the holder a reliable and unambiguous way of knowing whether the resource is valid and that the resource will remain valid while you use it.

You return that weak_ptr, and when the consumer actually wants to use the object, it converts the weak_ptr to a temporary shared_ptr. This will either fail (giving a null pointer) so the consumer knows that the object has been deleted, and it may not use it. Or, it will succeed, and now the consumer has a valid pointer with shared ownership of an object which is now guaranteed to remain valid while it is being used.

There is nothing in between "valid" and "invalid", no guessing, and nothing that can fail. If you successfully converted to a valid temporary shared_ptr, you are good to go. Otherwise, the object is gone, but you know that.
This is a big, big plus in terms of safety. Even if the manager "deletes" the object while you are using it, your program will not crash or produce garbage, the object remains valid until you stop using it!

Arguably, this somewhat blurs the "the manager deletes objects when it chooses to do so" paradigm, but it really is the only way of doing it safely. The manager is still the one in control of what objects to delete, it only cannot delete an object immediately while it is in use (which would possibly result in a terrible desaster). It can, however, at any time schedule the deletion for the next possible time by removing its shared_ptr and thus decrementing the reference count.

The only obvious showstopper is the case where an object must be destroyed immediately (because the destructor has side effects that must happen immediately without delay). But in this case, it is very hard (a nightmare!) to get concurrent access right. Luckily, that's a very rare scenario, too.

Damon
  • 67,688
  • 20
  • 135
  • 185
  • After reading this I feel like my design might be flawed. I certainly don't want to force delete an object just merely remove it from the vector if something should request the manager to remove an object by name. – Mister Apr 15 '14 at 17:38
  • Nothing wrong with deleting, as long as you do not delete the object while another part of code expects to be able to use the object via a pointer that it holds. Reference counting (or handing out weak pointers) is very nice in that respect because you don't have to worry. And since you explicitly said you don't want to "force delete", you are exactly good with these. They do just that, a kind of "soft delete", or a "schedule for delete". – Damon Apr 15 '14 at 17:41
2

Return a reference (or regular pointer) to the SomeObject from your function. The reference is valid so long as it remains in the vector, and the vector is not reallocated (careful with that, maybe use a list instead or vector of unique_ptr). When removed from the vector, the object is dead and all references to it are no longer valid. (Again careful removing element in the middle)

Neil Kirk
  • 21,327
  • 9
  • 53
  • 91
2

If you are not storing your objects as std::shared_ptrs, then it wouldn't make any sense to return an std::shared_ptr. Not even sure how you are going to do it. I don't think there is a way to wrap an already existing pointer within a smart pointer. If you already have the data there, you can just return a regular const pointer to it. That way you will avoid the overhead that it takes to copy the object contents.

santahopar
  • 2,933
  • 2
  • 29
  • 50
0

You have a choice of using shared_ptr or weak_ptr in this case. Which you use will depend on the lifetime you want for the object.

If you only want the object to be valid whilst the SomeObjectManager has a reference to it and a client is using it at that time then use weak_ptr. If you want a reference to remain valid if either the SomeObjectManager has a reference and a client stores a reference to it.

Here's an example with a weak_ptr.

std::weak_ptr<SomeObject> weakref = getSomeObject();   
// weakref will not keep the object alive if it is removed from the object manager.

auto strongref = weakref.lock();
if ( strongref ) {
     // strongref is a shared_ptr and will keep the object alive until it 
     // goes out of scope.
}

This can be useful in a multi-threaded environment as the shared_ptr reference count access is thread-safe. However, it does mean that a client can extend the lifetime of an object longer than you may like.

Steve
  • 7,171
  • 2
  • 30
  • 52
-1

If you want to use smart shared pointers, the vector itself should use the smart pointer.

class SomeObjectManager
{
private:
    std::vector<std::shared_ptr<SomeObject> > _heldObjects;
};

But then you're safe.

Bgie
  • 523
  • 3
  • 10
  • 1
    "I want the SomeObjectManager class to own the actual object being returned and never have said SomeObject be deleted unless the Manager makes it so." With shared pointers, the manager loses all ability to delete the object when it needs to. – juanchopanza Apr 15 '14 at 15:36
  • @juanchopanza: Well, no, it doesn't. It loses the ability to delete objects _immediately in an unsafe way_, but it retains the ability to delete objects safely and with concurrent readers. Actually, in a multithreaded program, this `shared_ptr` construct which hands out `weak_ptr`s would be the preferrable implementation (indeed the only safe one). – Damon Apr 15 '14 at 16:13