Reputation: 123263
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 pointerSomeUtilityClass
noticing itHow 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
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
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:
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
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
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
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