Question

I'm new to C++ and I have troubles wrapping my head around ownership, specifically with a getter. Here's some example code:

class GameObject {
public:
  Transform *transform();
private:
  Transform _transform;
};

I guess a raw pointer is unsafe to use as someone could access it later when the object doesn't exist anymore?

  1. So I thought about using a unique_ptr for the transform member, since GameObject is the only one that owns the transform. But I can't return that from the getter, can I? But then again, why would I ever use a unique_ptr in the first place instead of adding it as a member like above?

  2. So why not use a shared_ptr? It just seems wrong to me, I don't want to share ownership, GameObject is the owner and others may access it...

  3. So what is it? A reference? I guess shared_ptr seems the wisest choice, since others could safely keep a reference to transform, but what good is that if the enclosing GameObject got destroyed, rendering the transform useless? I'm probably just thinking about ownership the wrong wrong way here but every way seems wrong to me. Thanks for your help.

Was it helpful?

Solution

It is obvious (or should be obvious) to anyone reading your class definition that GameObject owns a transform. You are right that "shared ownership" is not implied or desired. As there is no possibility of ever not being able to obtain a transform from a GameObject you don't need something that expresses possible nullness like a pointer (raw or otherwise), so returning a reference (possibly) const seems the most idiomatic thing to do.

You say that a raw pointer is unsafe but it is no more unsafe than any other direct access method. Any way you provide access to the owned transform object (and not a copy of it) gives the client the chance to take its address and store that beyond the lifetime of the owning GameObject. It is really up to the client not to do dumb things. There is no way to prevent this absolutely, so you should make your interface simple and clear and hard to inadvertently use incorrectly.

OTHER TIPS

I think what is important is that your design models the fact that the Transform object is being owned by its GameObject.

If ownership is not meant to be shared (in other words, if the lifetime of the Transform object is strictly bound by the lifetime of the one and only GameObject that owns it), then I would avoid shared_ptr.

In my opinion, your first choice should be the one you actually picked already: just have a subobject of type Transform. However, return a reference to it rather than a raw pointer:

class GameObject {
public:
    Transform& transform();
private:
    Transform _transform;
};

This is the clearest possible way of communicating the fact that you are providing access to, yet not giving ownership of, the _transform subobject.

Clients do not have to bother with questions such as "when and how should I delete this object? And should I delete it at all?", because your interface is clear about it: no ownership, no responsibilites.

There are reasons, on the other hand, that may prevent you from doing this. One possible reason could be that a GameObject may or may not own a Transform object. If this is the case, you may want to use a unique_ptr instead (and return a raw pointer, which may be null).

Another reason for using a unique_ptr may be that the construction of the Transform subobject needs to be deferred because the constructor accepts some values that need to be computed, and could not be provided in the constructor's initialization list.

In general, unless you have a good reason for doing so, prefer the simplest solution and just embed a subobject of type Transform, giving away a reference.

Rule of thumb: you only bother with pointers and ownership when it's absolutely unaviodable. C++ have fine value semantics (extended with const ref), so you can get pretty far before wanting pointers, smart or dumb.

In your example _transform is very fine as direct member. If you want a getter, you can make it

Transform transform() const {return _transform; }

In some special cases it might be

const Transform& transform() const {return _transform; }

with documentation of lifetime. Only do that if justified.

Well, and obviously the best approach to getters is to not have them at all. Object orientation is about behavior, make your class do your work instead of using it as a data dump that you keep poking and querying from outside.

Ownership and raw pointers?

Smart pointers usually comes with ownership semantics, so using any of them when you don't want to give/share ownership is a bad idea.

In my own code, I decided the following convention:

raw pointers means no ownership transfer/sharing

This means that if some functions receives a raw pointer as a parameter, then it does not have ownership (and thus, should not try to delete it). In the same way, a function returning a raw pointer does not transfer/share ownership (thus, again, the caller should not try to delete it).

So in your case, you can return a pointer instead of a smart pointer.

Pointer or Reference?

This means the choice between pointer and reference depends only on one factor resolved by the following rule:

  • If a nullptr/NULL value makes sense, then use pointers.
  • If a nullptr/NULL value makes no sense and/or is undesirable, then use references.

constness and member variables?

I saw some answers on constness, so I add my own two-cents:

Returning a non-const value to an internal variable is a design choice. What you must do is to choose between the following getters:

const Transform * getTransform() const ;
Transform * getTransform() ;

I call (abusively) the accessors above a "property", and the non-const version enables the user to modify the internal Transform object at his/her leisure, without needing to ask your class first. You'll note that if the GameObject is const, there is not way to modify its internal Transform object (the only accessible getter is the const one, returning const). You'll note, too, that you can't change the address of the Transform object inside GameObject. You can only change the data pointer by that address (which is different from making the member variable public)

const Transform *& getTransform() const ;
Transform *& getTransform() ;

This is like the situation above, but thanks to the reference, the user have direct access to the pointer address, meaning the non-const accessor is, more or less, akin to make the member variable public.

const Transform * getTransform() const ;
void setTransform(Transform * p_transform) ;

I call (abusively) the accessors above a "getter/setter": If the user wants to modify the transform value, then he must use the setTransform. You have a lot more control on what you're doing (note that in my code, the setter you pass a unique_ptr or a auto_ptr to express the acquisition of total ownership on the p_transform object. You'll note that if the GameObject is const, there is not way to modify its internal Transform object (the setter is non-const).

Transform * getTransform() const ;

I call (abusively) the accessor above a "subscript", as in arrays of pointers, the array can be const, and thus the internal address is const, but the data pointed by this address is non-const, so it can be modified. This usually means that the Transform object is not really "owned" by your GameObject object. Indeed, it is probable GameObject only references some external object, for ease of use.

The others have already answered the question concerning the ownership but I still would like to add one more point.

Giving away a non-const pointer or reference to a private member is a usually a very bad idea: It is basically equivalent to making that private member public. In other words, if you write a getter returning a non-const reference or a pointer to a non-const member then your code is basically equivalent to the following:

class GameObject {
public:
  Transform _transform; // and you don't have to write a getter
};

But it is a bad idea.

If you really have to write a getter, give a const reference to the private member as proposed by Balog Pal. If you need a non-const reference, then you should reconsider your design.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top