463035818_is_not_an_ai
463035818_is_not_an_ai

Reputation: 123263

Why would I want to use a smart pointer in this situation?

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:

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.

Upvotes: 8

Views: 328

Answers (5)

Not a real meerkat
Not a real meerkat

Reputation: 5739

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.

Upvotes: 1

Sergei Tachenov
Sergei Tachenov

Reputation: 24919

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.

Upvotes: 0

Wojtek Surowka
Wojtek Surowka

Reputation: 21003

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.

Upvotes: 1

TartanLlama
TartanLlama

Reputation: 65770

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.

Upvotes: 2

Bill Lynch
Bill Lynch

Reputation: 81996

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.

Upvotes: 1

Related Questions