How to Best Implement Objects with "Optional" Functionality?

Started by
12 comments, last by dmatter 9 years ago

Of course it's up to you, but just for smart pointers I would not bring boost into a codebase, since C++ already provides them, YMMV.

Advertisement

Raw pointers are for when you want a nullable, non-owning reference. The nullable property allows you to implement the optional semantic. But the non-owning property doesn't sound like it's what you want. So I doubt that a raw pointer is really the best solution here.

I read his discussion, use your same initial logic, and come to the opposite conclusion.

The Location may or may not own the object, perhaps the world map owns the object. Either way, that is the parent of the object.

I'm looking at the ownership based on this:

Depending on whether or not the Location has a Safehouse when the player is interacting with the Location, the game/UI/menu/whatever will display different options to the player. i.e. Right now I'm working off the assumption that if a Menu should display certain things based on the existence of a Safehouse or not, then the Menu should be able to query somehow whether or not a valid Safehouse actually exists.


Effectively I see code that looks like this:

Safehouse *safehouse = location->GetSafehouse();
if(safehouse) {...}

The object greatly outlives the very short immediate usage, and the option of being null is a strong possibility. That is both nullable and non-owning, so a raw pointer is perfect.

For rendering, in all the engines and products I've worked with those pointers are completely unrelated to what is rendered. If you need to get something to display on a minimap or UI or menu, the code reaches in to a resource pool and retrieves a proxy for the object (which can be streamed in/out, cached, reused, translated, and so on). The proxy is owned by the corresponding resource store and also greatly outlives its usage, so the pointer to the proxy is passed to the minimap or UI or menu as needed.

Since the OP mentioned the option of returning a collection of objects, I would select that option only if the design allowed for more than one object. As described you can either return a single object or null. If you need multiple you would return a collection with zero or more Safehouse pointers.


class Location
{
public:
    Location()
      : _safehouse(new Safehouse())
      , _bank(new Bank())
    { }

    Location(Safehouse * safehouse, Bank * bank)
      : _safehouse(safehouse)
      , _bank(bank)
    { }

    ~Location() {
        delete _safehouse;
        delete _bank;
    }

private:
    Safehouse * _safehouse;
    Bank * _bank;
};

I'm imagining a Location might eventually end up like this:


class Location
{
    Safehouse*  _safehouse;
    Bank*       _bank;
    Farm*       _farm;
    Barracks*   _barracks;
    Watchtower* _watchtower;
    // etc...
};

I'm curious, will this be a problem in your game? How many different things might be in a Location? Maybe if there are just SafeHouses it doesn't matter, but I hope this is not something that will be used for dozens of other optional buildings.

Raw pointers are for when you want a nullable, non-owning reference. The nullable property allows you to implement the optional semantic. But the non-owning property doesn't sound like it's what you want. So I doubt that a raw pointer is really the best solution here.

I read his discussion, use your same initial logic, and come to the opposite conclusion.

The Location may or may not own the object, perhaps the world map owns the object. Either way, that is the parent of the object.

Yeah, as you note, it is hard to know who owns what from the original post. But the OP gives us a clue us in a later post, as I quoted:

Really? Kewl. Maybe I wasn't so naive after all... making sure the destructor deleted the pointer was what I originally had assumed was the right (and only necessary) thing to do.

Based on that I came to the conclusion that if the OP was planning to have the destructor delete the pointer then the Location must be the owner of the object. So a non-owning pointer/reference would be inappropriate.

Effectively I see code that looks like this:

Safehouse *safehouse = location->GetSafehouse();
if(safehouse) {...}

The object greatly outlives the very short immediate usage, and the option of being null is a strong possibility. That is both nullable and non-owning, so a raw pointer is perfect.

Arguably the Location object could be sharing ownership of the Safehouse (rather than lacking any ownership) - For me it isn't clear either way from such a small snippet.
If you're saying that the lifetime of the Location object is always "within" the lifetime of the Shafehouse object then I can agree that Location doesn't absolutely need shared ownership so non-owning raw-pointers would suffice. However unless there's a good reason not to (e.g. necessary optimizations) then I would still prefer using shared_ptrs since shared ownership is easier to reason about and will be more resilient to future refactorings.

For rendering, in all the engines and products I've worked with those pointers are completely unrelated to what is rendered. If you need to get something to display on a minimap or UI or menu, the code reaches in to a resource pool and retrieves a proxy for the object (which can be streamed in/out, cached, reused, translated, and so on). The proxy is owned by the corresponding resource store and also greatly outlives its usage, so the pointer to the proxy is passed to the minimap or UI or menu as needed.

I'm on-board with the idea that if the overall design makes it readily apparent that an object has extreme longevity well beyond all uses then the rest of the system can deal in non-owning pointers without any risk. This typically applies to pool-managed resources like you say. The way I see it a pool is a centralised memory management approach where there is only 1 owner (the pool) and many non-owners (the rest of the system using non-owning raw-pointers) , whereas shared_ptrs are a de-centralised approach in which everybody must opt into the approach by using the shared_ptr instead of a raw pointer.

This topic is closed to new replies.

Advertisement