HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Proposed solution to dangling pointers: a non-owning smart pointer

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
nonowningsmartpointerdanglingproposedpointerssolution

Problem

The lifetimes of various objects referring to each other are sometimes only known at runtime. For example, in a side scrolling shooter game, a HomingMissile instance might have a pointer to the Target instance it is following. But if the Target is killed before the missile arrives, the HomingMissile is left with a dangling pointer to where the Target was in memory, likely causing a segmentation fault.

It would be nice if pointers could just be checked for validity, but it is obvious that the following example for detecting wether or not ptrToFoo is valid by comparison with nullptr will fail, as a pointer is merely an address, having no knowledge about the lifetime of the pointed object.

struct Foo
{
    const char *hello() const
    {
        return "hello world";
    }
};

int main()
{
    Foo *foo = new Foo;
    Foo *ptrToFoo = foo;
    std::cout hello()) hello()) << "\n";
    return 0;
}
//output:
//hello world
//hello world     <- bad


C++11 smart pointers are great, but they do not feel like the right tool for the job. It makes no sense for the HomingMissile to own the Target trough a std::shared_ptr or a std::unique_ptr. Having HomingMissile hold a std::weak_ptr to the Target does allow checking the state of the target instance, but it forces the Target to be owned trough a std::shared_ptr by some unrelated third party. In addition, constantly calling weak_ptr::lock() is cumbersome at least.

As a solution, I have been writing a different kind of smart pointer, one that does not imply any kind of ownership at all. The idea is to have a reference-count-based pointer-like class that knows if the pointed object has been destroyed or not, preventing the "dangling pointer" problem:

```
struct Foo : public ObserverPtr::Observable
{
const char *hello() const
{
return "hello world";
}
};

int main()
{
Foo *foo = new Foo;
ObserverPtr ptrToFoo(*foo);
std::cout hello()) hello()) << "\n";
r

Solution

Foo *foo = new Foo;


This isn't safe: an exception later on will leak the object.

///////////////////////////////////////////////////////////////////////


These waste vertical space which makes to harder to read.

void resetObserverPtrs()


This isn't called from anywhere.

void reset(const T &observed)


This isn't called from anywhere; but it is public. Perhaps it should be an assignment operator=.

I'm not sure why you have if(tracker_ != nullptr) in this method: IMO observed.tracker_ can never be null (unless observed has been destroyed, in which case it should not have been passed as a parameter to this method).

void swap(ObserverPtr &other)


Maybe use std::swap to implement this method.

T *get() const


Instead of undefined behaviour, if (expired()) return nullptr;.

Also, you can use standard shared_ptr and weak_ptr instead of 'reinventing the wheel'. Your ObserverPtr is analogous to a weak_ptr, and your ObserverPtr::Observable is analogous to a shared_ptr.


However, while Foo *foo = new Foo; is not safe, does it really matter?

It's up to you whether it matters: see this meta-answer for details.


It's just a short usage example

Then it's an example of unsafe usage, is all I'm saying. What questions are on-topic for this site? says, "Is it actual code from a project rather than pseudo-code or example code?"

You also said, "The ObserverPtr implementation should work ... independently of allocation type (stack, heap, gobal)" and said you don't want to use standard smart pointers: so maybe you're intending to use Foo* in your real code as well as in your example code.

Another problem, which I didn't mention previously, is that because Observable contains a naked Tracker* pointer, you should delete or override the copy constructor and assignment operator for Observable. Deleting those will make it difficult to store Observable instances in standard containers. You can store naked pointers to Observable instances (e.g. std::list though not std::list, but naked pointers aren't exception safe.

Code Snippets

Foo *foo = new Foo;
///////////////////////////////////////////////////////////////////////
void resetObserverPtrs()
void reset(const T &observed)
void swap(ObserverPtr &other)

Context

StackExchange Code Review Q#45025, answer score: 5

Revisions (0)

No revisions yet.