8

I never used any kind of smart pointer, but I keep reading about them almost everywhere when the topic is pointers. I do understand that there are situations where smart pointers are much nicer to work with than raw pointers, because to some extend they manage ownership of the pointer. However, I still do not know, where is the line between "I do not needing smart pointers for that" and "this is a case for smart pointers".

Lets say, I have the following situation:

class A {
public:
    double get1(){return 1;}
    double get2(){return 2;}
};
class SomeUtilityClass {
public:
    SomeUtilityClass(A* a) : a(a) {}
    double getResult(){return a->get1() + a->get2();}
    void setA(A* a){a = a;}
private:
    A* a;
};
int main(int argc, char** argv) {
    A a;
    SomeUtilityClass u(&a);
    std::cout << u.getResult() << std::endl;
    A a2;
    u.setA(&a2);
    std::cout << u.getResult() << std::endl;
    return 0;
}

This is of course an oversimplified example. What I mean is that SomeUtilityClass is not supposed to "own" an instance of A (because it is just a utility class), thus it just holds a pointer.

Concerning the pointer, the only thing that I am aware of that could go wrong is:

  • SomeUtilityClass can be instantiated with a null pointer
  • The object pointed to may be deleted/go out of scope, without the SomeUtilityClass noticing it

How could a smart pointer help to avoid this problem? What other benefits I would get by using a smart pointer in this case?

PS: I know that there are several question on smart pointers (e.g. this one). However, I would appreciate, if you could tell me about the impact on this particular example.

Community
  • 1
  • 1
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 7
    Raw pointers perfectly capture the concept of a non-owning reference to data with a scoped lifetime. You shouldn't use smart pointers here. – Mankarse May 04 '15 at 13:40
  • 5
    If you're using `new` in the wild, that's a pretty strong hint you may need a smart pointer. If you're using `new []`, that's a pretty strong hint you may need a `std::vector` or another container. – JBL May 04 '15 at 13:41
  • 1
    If a `nullptr` does not make sense, you might consider a reference or `std::reference_wrapper` instead. – Baum mit Augen May 04 '15 at 13:42
  • 5
    As an aside: Could it be that `void setA(A* a){a = a;}` doesn't do what you want? I sometimes prefix members with "m"; that serves both as an indicator when reading code and a natural "namespace" which distinguishes members from other names. In C# it would probably be an underscore. – Peter - Reinstate Monica May 04 '15 at 13:43
  • 1
    If you want to make sure that the pointer is alive for the lifetime of the class at a minimum you should use a shared pointer – NathanOliver May 04 '15 at 13:44
  • I always do this with raw pointers. – Daniel Daranas May 04 '15 at 14:28
  • @NathanOliver I was under the impression (but may be wrong) that smart pointers are meant to be used with dynamically allocated memory. If the only `shared_ptr` wrapping `&a` goes out of scope it may well try to delete `&a` which would be no good. At a minimum it must be a weak_ptr. Would that make any sense? One could change `setA()`'s signature to take a weak_ptr and wrap `&a` in a weak_ptr of the same scope as an argument to `setA()`. Then at least one would have an indication when &a is invalid (because before `a` is destroyed, the weak_ptr is). Any opinions? – Peter - Reinstate Monica May 04 '15 at 14:42
  • @PeterSchneider A shared pointer AFAIK wont work with this situation. I was just suggesting that if the OP needs that behavior they you use a shared pointer solution. – NathanOliver May 04 '15 at 14:44
  • @NathanOliver Nothing keeps you from creating a shared_ptr from `&a`. But that is not what you mean, I guess -- when you say "shared pointer solution" you also imply "dynamically allocated objects", don't you? – Peter - Reinstate Monica May 04 '15 at 14:51
  • @PeterSchneider Yes. The pointer is dynamically allocated and is passed to the class as a shared pointer. – NathanOliver May 04 '15 at 14:52

5 Answers5

2

This depends on how the parameter is created and stored. If you don't own the memory and it could be either statically or dynamically allocated, a raw pointer is a perfectly reasonable solution -- especially if you need to support swapping of the data as in your example. Another option would be to use std::reference_wrapper, which would get rid of your nullptr issue whilst keeping the same semantics.

If you are holding a pointer to some shared resource (i.e. stored in a std::shared_ptr somewhere) and want to be able to check if it has been deleted or not, you could hold a std::weak_ptr.

TartanLlama
  • 63,752
  • 13
  • 157
  • 193
1

If SomeUtilityClass does not own the member variable a, then a smart pointer does not make sense.

You might consider a reference member, which would remove the problems of a null pointer.

Bill Lynch
  • 80,138
  • 16
  • 128
  • 173
1

The default way of expressing not-owning pointer in C++ is weak_ptr. To use weak_ptr you need to use shared_ptr for ownership, so in your example you would use

shared_ptr<A> owner(...)

instead of

A a

Then as the private pointer member of your SomeUtilityClass you use weak pointer:

weak_ptr<A> w;

and initialise it with shared_ptr:

SomeUtilityClass(shared_ptr<A> o) : w(o) {}

however, you cannot use weak_ptr directly, since the shared_ptr could go out of scope and your weak pointer can no longer point to anything. Before use you need to lock it:

shared_ptr<A> locked = w.lock();

The locked pointer will be empty if the owning pointer no longer manages an object, since e.g. it went out of scope. If it is not empty, you may use it and then it will go out of scope automatically releasing the lock the object.

Both shared_ptr and weak_ptr are available in standard library in C++11, and in Boost for older compilers.

Wojtek Surowka
  • 20,535
  • 4
  • 44
  • 51
  • 1
    `weak_ptr` isn't the default way to express non-owning. It's just used to break cycles when `shared_ptr` is used. The default way to express non-owning is a raw pointer. – Sebastian Redl May 04 '15 at 14:06
  • @SebastianRedl You stated your personal opinion as it were generally accepted. However, [cppreference](http://en.cppreference.com/w/cpp/memory/weak_ptr) says _std::weak_ptr models temporary ownership: when an object needs to be accessed only if it exists_ and later _In addition, std::weak_ptr is used to break circular references_, so at least there - and in many other places - different opinions are expressed. – Wojtek Surowka May 04 '15 at 14:10
  • And I used my personal reputation to downvote your answer. This is how SO works. Also, how do you read "models temporary ownership" as supporting your stated point of "default way of expressing not-owning pointer"? If anything, cppreference *contradicts* your post, and *supports* my comment. – Sebastian Redl May 04 '15 at 14:16
  • @SebastianRedl Not at all. You said that weak_ptr is "just for" breaking cycles. Cppreference says that this is its _additional_ purpose. The main purpose is _temporary_ ownership. And if you want to use a pointer - any pointer, smart or raw - you better own it _while you use it_, otherwise it could happen that your object no longer exists while you use it, couldn't it? So of course weak_ptr is non-owning, only when you want to use it you - conditionally - get the ownership for a time being. This is safe. Raw pointers proposed by you are not. – Wojtek Surowka May 04 '15 at 14:26
  • Ok on the "just for". It's also for observing something that might go away. It's not the default, though. However, "And if you want to use a pointer - any pointer, smart or raw - you better own it while you use it," is wrong - borrowing resources (i.e. using them while not owning them) is very common, and quite safe if you use scope to establish that *someone* owns the object. For example, a pointer argument to a function is generally safe (as safe as a non-GC language gets) if the pointer isn't kept beyond the function call, because the caller can easily keep the object alive. – Sebastian Redl May 04 '15 at 14:39
  • @SebastianRedl The cases you listed are all examples of temporary ownership. Try to understand that ownership != temporary ownership. The former means "I control the lifetime of it". The latter "I borrowed it, and it will live while I am using it". Weak_ptr allows to express it in a safe way. (BTW using pointer arguments to return values is idiomatic enough to be safe too). – Wojtek Surowka May 04 '15 at 14:49
1

For the purposes of this answer I'm redefining setA as:

void setA(A* new_a){a = new_a;}

Consider:

// Using your SomeUtilityClass

int main() {
  A a;
  SomeUtilityClass u(&a);
  // We define a new scope, just because:
  {
    A b;
    u.setA(&b);
  }
  std::cout << u.getResult() << '\n';
  return 0;
}

After the scope is finished, SomeUtilityClass has a dangling pointer and getResult() invokes Undefined Behaviour. Note that this can't be solved with a reference: You would still get a dangling one.

Now consider the version using a smart pointer:

class SomeUtilityClass {
public:
    SomeUtilityClass(std::shared_ptr<A>& a) : a{a} {}
    double getResult(){return a->get1() + a->get2();}
    void setA(std::shared_ptr<A>& new_a){a = new_a;}
private:
    std::shared_ptr<A> a;
};

int main() {
  std::shared_ptr<A> a{new A};
  SomeUtilityClass u{a};
  // We define a new scope, just because:
  {
    std::shared_ptr<A> b{new A};
    u.setA(b);
  }
  std::cout << u.getResult() << '\n';
  return 0;
}

Because you have shared ownership, there's no way to get a dangling pointer. The memory pointed to by b will be deleted as usual, but only after u is destroyed(or its pointer is changed).

IMHO, in most cases you should be using smart pointers (Even when at first it doesn't seem to make much sense). It makes maintenance much easier. Use raw pointers only in specific code that actually needs them, and encapsulate/isolate this code as much as possible.

Not a real meerkat
  • 5,604
  • 1
  • 24
  • 55
0

There are different types of smart pointers. In your case, it is clear that a smart pointer is not really needed, but it may still provide some benefits.

SomeUtilityClass can be instantiated with a null pointer

This one is probably best solved with a check in the constructor, throwing an exception or indicating an error in some other way in the case when you get a NULL pointer as the argument. I can hardly imagine how a smart pointer would help, unless you use a specific smart pointer class that doesn't accept NULLs, so it does the check for you already.

The object pointed to may be deleted/go out of scope, without the SomeUtilityClass noticing it

This one can actually be resolved with a special type of smart pointers, but then it is needed that the object being pointed to somehow supports notification of destruction. One such example is the QPointer class in the Qt library, which can only point to QObject instances, which notify it when deleted, so the smart pointer automatically becomes NULL when the object is deleted. There are some problems with this approach, though:

  1. You need to check for NULLs every time you access the object through the smart pointer.
  2. If a smart pointer points to an instance of a class, say MyClass, extending the class performing the deletion notification (QObject in the Qt case), you get strange results: it's the destructor of QObject that notifies the smart pointer, so it is possible that you access it when the MyClass destructor already began its dirty work, so the object is partially destructed, but the pointer is not NULL yet because the destruction is still in progress.
Sergei Tachenov
  • 24,345
  • 8
  • 57
  • 73
  • 1
    Now, I normally ignore downvotes, but in this case I have clearly replied to the points indicated by the OP, providing an example from a widely used C++ library. What on Earth is so wrong with my answer that somebody sacrificed a whole point of reputation just to tell the world how bad and dangerous it is? Perhaps you tell me and I fix it? – Sergei Tachenov May 04 '15 at 14:11