Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Suicide object implementation leveraging `std::weak_ptr`

I'm considering using "suicide objects" to model entities in a game, that is, objects able to delete themselves. Now, the usual C++03 implementation (plain old delete this) does nothing for other objects potentially refering to the suicide object, which is why I'm using std::shared_ptr and std::weak_ptr.

Now for the code dump :

#include <memory>
#include <iostream>
#include <cassert>

struct SuObj {
    SuObj() { std::cout << __func__ << '\n'; }
    ~SuObj() { std::cout << __func__ << '\n'; }

    void die() {
        ptr.reset();
    }

    static std::weak_ptr<SuObj> create() {
        std::shared_ptr<SuObj> obj = std::make_shared<SuObj>();
        return (obj->ptr = std::move(obj));
    }

private:

    std::shared_ptr<SuObj> ptr;
};

int main() {
    std::weak_ptr<SuObj> obj = SuObj::create();

    assert(!obj.expired());
    std::cout << "Still alive\n";

    obj.lock()->die();

    assert(obj.expired());
    std::cout << "Deleted\n";

    return 0;
}

Question

This code appears to work fine. However, I'd like to have someone else's eye to gauge it. Does this code make sense ? Did I blindly sail into undefined lands ? Should I drop my keyboard and begin art studies right now ?

I hope this question is sufficiently narrowed down for SO. Seemed a bit tiny and low-level for CR.

Minor precision

I do not intend to use this in multithreaded code. If the need ever arises, I'll be sure to reconsider the whole thing.

like image 290
Quentin Avatar asked Jan 09 '15 17:01

Quentin


1 Answers

When you have shared_ptr based object lifetime, the lifetime of your object is the "lifetime" of the union of the shared_ptrs who own it collectively.

In your case, you have an internal shared_ptr, and your object will not die until that internal shared_ptr expires.

However, this does not mean you can commit suicide. If you remove that last reference, your object continues to exist if anyone has .lock()'d the weak_ptr and stored the result. As this is the only way you can access the object externally, it may happen1.

In short, die() can fail to kill the object. It might better be called remove_life_support(), as something else could keep the object alive after said life support is removed.

Other than that, your design works.


1 You could say "well, then callers should just not keep the shared_ptr around" -- but that doesn't work, as the check that the object is valid is only valid as long as the shared_ptr persists. Plus, by exposing the way to create shared_ptr, you have no type guarantees that the client code won't store them (accidentally or on purpose).

A transaction based model (where you pass a lambda in, and it operates on it internally) could help with this if you want seriously paranoid robustness.

Or you can live with the object sometimes living too long.


Consider hiding these messy details behind a Regular Type (or almost-regular) that has a pImpl to the nasty memory management problem. That pImpl could be a weak_ptr with the above semantics.

Then users of your code need only interact with the Regular (or pseudoRegular) wrapper.

If you don't want cloning to be easy, disable copy construction/assignment and only expose move.

Now your nasty memory management is hiding behind a fascade, and if you decide you did it all wrong the external pseudoRegular interface can have different guts.

Regular type in C++11

like image 121
Yakk - Adam Nevraumont Avatar answered Sep 21 '22 16:09

Yakk - Adam Nevraumont