Can someone please explain + example of 'ownership'

Started by
10 comments, last by phil_t 8 years, 5 months ago

I am trying to understand 'ownership' in c++. This term keeps coming up when I google around trying to understand smart pointers, the explanations I mostly come across stress 'ownership'. Mostly the examples say something like "A owns B" use ... pointer, or if you need to share ownership, use ... pointer. I never understand what are A and B.. are they objects? if so what does it mean A owns B? Like some code example? Why would you ever 'share' ownership? etc etc...

In my self-learning of C++ (a book and learning game dev stuff using source code and hacking together my own 2d space shooting game)... I never really came across ownership (or maybe I do but just don't realize it). What it is? why its important?, what the rules are?, and most importantly why it matters for me when I am doing hobbiest game programming.

So can someone really break it down to an ultimate noob such as myself? And what of this do we need to take into considerations for 'Game Dev'.. e.g. a List of items? Player positions? Map loading..?

Thanks all

Advertisement

A simple explanation of ownership can be seen like a cup class and liquid class : the cup owns the liquid.

That could be like that :


class Cup;

class Liquid
{
public:
  Liquid( Cup* Owner ) :
  m_Owner( Owner )
  {
  }

  Cup* GetOwner() const
  {
    return m_Owner;
  }

private:
  Cup* m_Owner;
}

Now you can get the owner from liquid to know what cup owns this liquid.

A and B could be anything. They could be objects. They could be a handle to an operating system resource. They could be an opaque type you need for interaction with a library.

Ownership and the proper definition of it is pretty much a core issue in software development. If you get ownership right a lot of problem sources are just gone.

You always have to deal with ownership (at least for non-trivial programs). In the simplest case whenever you 'new' something the immediate question you need to ask it: "and who does the 'delete' now?" (the 'when?' is also an interesting problem but usually just another facet of the same core issue).

Common problems if you don't get ownership right include:
  • memory leaks
  • freeing resources more than once
  • accessing resources after they have already already been freed
Finding the right ownership semantics for a problem is part of being an experienced developer. There is no 'The Solution', you need to look at your situation and weigh pros and cons of possible solution to arrive at ownership semantics.

For example if several models you are rendering use the same texture "rusty_metal_grate.png" it would be a waste to upload it for each model which needs it. They could all share it. So, instead of each model loading the texture from disk and uploading it to video memory they ask the AssetCache for it. The AssetCache either loads the texture from disk and instantiates it or returns an already existing instance. When a model is done with it, it does not destroy the texture (that would mean every other model using the texture is broken, most likely crashing the application when they next try to use it), it informs the AssetManager they no longer need it. In most cases the AssetManager will then just note down that the resource is used by one less user. Only if the last user releases the resource it could consider freeing it.

In other situations you explicitly don't want to share, for example because each owner wants to make modifications to a resource which could be contradictory. Or liable to race conditions because each owner lives in a different thread and you cannot/don't want to enforce some kind of synchronization.

You can fill whole topics of a book with discussions about the concept without exhausting it. In the context of C++ RAII is an important related subject as is the Rule of Three. It might also be a good idea to take a look at the standard library's std::shared_ptr and std::unique_ptr.

Simply put, the owner is the one responsible for releasing the resource when everyone is done using it.

It is important to keep track of, because it is an error to try to release the same resource twice, and will most of the time crash your program.

Its also an error to not release the resource. It's called a leak, and with enough leakage, the whole system will go down eventually.

generally speaking, the resource could be anything, another object, a block of memory, a file handle, a network socket....

A simple explanation of ownership can be seen like a cup class and liquid class : the cup owns the liquid.
That could be like that :



class Cup;

class Liquid
{
public:
  Liquid( Cup* Owner ) :
  m_Owner( Owner )
  {
  }

  Cup* GetOwner() const
  {
    return m_Owner;
  }

private:
  Cup* m_Owner;
}
Now you can get the owner from liquid to know what cup owns this liquid.


That is not a useful example at all. None of the important bits can be gleaned from it. For the core concept of ownership the interesting part would be the definition of Cup. When is a liquid created? When is it destroyed? What happens when you copy a Cup? Are you allowed to copy a Cup? Can two Cup instances share the same Liquid?

[REMOVED]

What you wrote was just an example of a relation between two classes you chose to call ownership (you could as well have called them 'holder' and lose even the extremely superficial relationship to the question). It has nothing to do with the questions asked by the OP which were about the core concept of software development. In most cases resources being owned by someone will not know who owns them (and not care about it at all).

As I said, it was just a simple explanation to show the concept of ownership, I didn't go in details.

There are two types of ownership being answers here. You're describing ownership as in a parent/child relationship in an object hierarchy while BitMaster and Olof are describing ownership as in object lifetime management.

Object lifetime management has nothing to do with parent/child relationships but about when and how dynamic resources are released to avoid, for example, memory leaks when you forget to free on an allocated object.

[REMOVED]

The other key terminology you need to understand ownership is lifetime.

Every object has a lifetime. That is defined as the time between when it is initially created and when it is destroyed. For variables in a function, this is automatically managed by the compiler for you, and has been since the dawn of C++.

void function() {
  MyObject A; // A begins its lifetime here
  MyObject* ptr;

  while (condition) {
    MyObject B; // B begins its lifetime here

    ptr = &B;
    other_function();

  } // B ends its lifetime here, as this is the end of the scope in which B was declared

  A = *ptr; // BUG!! ptr points to an object whose lifetime is over, so ptr points to garbage!

} // A ends its lifetime here, as this is the end of the scope in which A was declare
That illustrates lifetimes for simple C++ code, and also illustrates a bug that happens when you don't pay attention to lifetimes.

Again, the above was all automatic. The compiler determined when lifetimes began and end based on declarations and scopes. There's nothing special you have to do other than to be sure not to break the rules (like the bug in the code sample).

Ownership is a term that means that the lifetime of one object is managed explicitly by user code instead of the compiler.

Local variables are always managed by the compiler, so clearly this must mean heap-allocated objects (e.g. things that directly or indirectly allocated with operator new or the like).

void function() {
  MyObject* A; // A does not yet refer to any object.
  MyObject* B;

  A = new MyObject; // A now refers to a new object, so we can say the lifetime of *A begins now
  B = A;

  some_function(*A);

  delete A; // we have explicitly deleted *A, so its lifetime is now over
  delete B; // BUG!! B pointed to the same object A did, which we already deleted!
}
Here we see explicit lifetime management. Note that 'A' and 'B' themselves are local variables that have automatic lifetime, but the object *A pointed to by A has explicit lifetime.

In this case, we would say that *A is owned by A. If A doesn't delete the object then you will probably leak, because nothing else in the code knows that it's supposed to delete *A.

However, there's danger here: you might forget to delete the object or you might lose track of who "owns" the object (leading to the bug were we tried to double-delete the same object).

Smart pointers automatically delete things, basically speaking. Smart pointers avoid both possible bugs above; they explicitly know they own another object and hence know when to delete the object safely... IF you don't do bad things with smart pointers.

The trick is just to understand these rules and problems. If you understand that objects all have lifetimes, and that only one piece of code "owns" each object and is responsible for deleting it, you can safely use smart pointers (and regular pointers): smart pointers just become a convenience to help you avoid forgetting or losing track.

Sean Middleditch – Game Systems Engineer – Join my team!

This topic is closed to new replies.

Advertisement