Jump to content

Ultra Software Company Blog

  • entries
    188
  • comments
    1,259
  • views
    699,886

Contributors to this blog

3 Ideas You Must Understand to Use C++11 Shared Pointers in Leadwerks Game Engine 5


Josh

3,504 views

 Share

Leadwerks Game Engine 5 is being designed to make use of shared pointers.  This eliminates manual reference counting, which has probably been the most difficult part of programming games with Leadwerks.  Here are three concepts you must understand before you start using smart pointers in Leadwerks 5,

Don't Create Multiple Shared Pointers from One Object

When a shared pointer goes out of scope, it deletes the object it references.  If another smart pointer was created separately that references that object, the other smart pointer will now point to an object that has been deleted!  Set breakpoints in the example below and you will see the problem.  The object is deleted while the second smart pointer still references it.  Any attempt to use the second smart pointer will cause an error:

class Thing
{
public:
	~Thing();
};

Thing::~Thing() {}// <--------- set a breakpoint here

int main(int argc, const char *argv[])
{
	Thing* thing = new Thing;

	shared_ptr<Thing> p1 = shared_ptr<Thing>(thing);
	shared_ptr<Thing> p2 = shared_ptr<Thing>(thing);

	p1 = nullptr;
	int k = 0;// <--------- set a breakpoint here
}

Instead, initialize a smart pointer once and copy it.  Here is the correct way:

class Thing
{
public:
	~Thing();
};

Thing::~Thing() {}// <--------- set a breakpoint here

int main(int argc, const char *argv[])
{
	Thing* thing = new Thing;

	shared_ptr<Thing> p1 = shared_ptr<Thing>(thing);
	shared_ptr<Thing> p2 = p1;

	p1 = nullptr;
	int k = 0;// <--------- set a breakpoint here
}

It's even better to eliminate the new keyword entirely and create object and smart pointer in one step:

class Thing
{
public:
	~Thing();
};

Thing::~Thing() {}// <--------- set a breakpoint here

int main(int argc, const char *argv[])
{
	shared_ptr<Thing> p1 = make_shared<Thing>();
	shared_ptr<Thing> p2 = p1;

	p1 = nullptr;
	int k = 0;// <--------- set a breakpoint here
}

The point is, you create the first smart pointer and thereafter all code should pass that around.  You never need to access the pointer directly.

Of course the use of auto makes everything a lot simpler:

auto p1 = make_shared<Thing>();
auto p2 = p1;

Parent / Child Relationships

If you have an object that is some kind of "child" of a parent object, you probably want that parent to keep a smart pointer to the child that keeps the child from being deleted.  However, if the child has a smart pointer to the parent you are creating a circular reference that will never be deleted from memory.  Think of the parent as the owner of the child.  Something other than the child must keep the parent in memory, but sometimes the child wants to retrieve the parent object in a bit of code.  Therefore, for the child member we will use a shared pointer, and for the parent member we will use a weak pointer:

class Thing
{
public:
	shared_ptr<Thing> child;
	weak_ptr<Thing> parent;
	~Thing();
	shared_ptr<Thing> GetParent();
};

The GetParent() function would look like this:

shared_ptr<Thing> Thing::GetParent()
{
	return parent.lock();
}

You can modify existing functions that access a parent by adding one line of code.  Here is one such function as it would appear in Leadwerks 4, where the parent member is just a regular old stupid pointer:

void Thing::AccessParent()
{
	if (parent != NULL)
	{
		//do some stuff to parent here
	}
}

The updated version for Leadwerks 4 creates a new shared_ptr<Thing> variable (with auto) and locks the weak pointer to make a shared pointer.  The rest of the code works seamlessly:

void Thing::AccessParent()
{
	auto parent = this->parent.lock();
	if (parent != NULL)
	{
		//do some stuff to parent here
	}
}

Class Functions Should Never Returns Themselves

The following code illustrates a problematic issue:

class Thing
{
public:
	shared_ptr<Thing> GetSelf()
};

shared_ptr<Thing> Thing::GetSelf()
{
	return shared_ptr<Thing>(this);
}

int main(int argc, const char *argv[])
{
	shared_ptr<Thing> p1 = make_shared<Thing>();
	shared_ptr<Thing> p2 = p1->GetSelf();
}

The GetSelf() function creates a new shared pointer that has no relation to the first one.  Both of these shared pointers will attempt to delete the object when they go out scope.  Only one will win. :blink:

I did a search throughout the entire Leadwerks Engine project and found only three instances of the phrase "return this;".  The easiest way to fix this problem would be to eliminate this type of behavior altogether by having a parent get the object instead of the object returning itself.  For example if you have a recursive function that is structured like this:

Thing* Thing::FindChild(const std::string& name)
{
	if (name == this->name) return this;
	for (auto it = kids.begin(); it != kids.end(); it++)
	{
		auto child = (*it)->FindChild(name);
		if (child) return child;
	}
	return NULL;
}

You can restructure it like this:

shared_ptr<Thing> Thing::FindChild(const std::string& name)
{
	for (auto it = kids.begin(); it != kids.end(); it++)
	{
		if (name == (*it)->name) return this;
		auto child = (*it)->FindChild(name);
		if (child) return child;
	}
	return NULL;
}

I considered deriving all complex objects from a "SharedObject" class with a weak pointer that referenced itself:

class SharedObject
{
	weak_ptr<SharedObject> self;
};

However, this requires the "self" member to be set when the object is created and is tedious to use.  I think it's easier to just eliminate functions that return the object itself.

  • Upvote 1
 Share

3 Comments


Recommended Comments

The problem you're describing in the end can be solved with by deriving from "std::enable_shared_from_this" (http://en.cppreference.com/w/cpp/memory/enable_shared_from_this). This basically does what you planned with the weak_ptr-stuff but without additional overhead on your side.

Translating your code-example, this would be:

#include <memory>
#include <iostream>
using namespace std;
class Thing : public enable_shared_from_this<Thing>
{
public:
	shared_ptr<Thing> GetSelf();
};

shared_ptr<Thing> Thing::GetSelf()
{
	return shared_from_this();
}

int main(int argc, const char *argv[])
{
	shared_ptr<Thing> p1 = make_shared<Thing>();
	shared_ptr<Thing> p2 = p1->GetSelf();
}

EDIT: It is important to derive publicly! In the example of the cppreference-link, they are using a struct, which derives publicly by default. For classes, you have to explicitly write ": public". I edited the code above

Edited by Ma-Shell
  • Upvote 2
Link to comment

Great, thanks for pointing that out!  I have verified this works as expected:

#include <memory>
#include <iostream>

class Thing : public enable_shared_from_this<Thing>
{
public:
	~Thing();
	shared_ptr<Thing> GetSelf();
};

Thing::~Thing() {}// <-- set breakpoint here

shared_ptr<Thing> Thing::GetSelf()
{
	return shared_from_this();
}

int main(int argc, const char *argv[])
{
	shared_ptr<Thing> p1 = make_shared<Thing>();
	shared_ptr<Thing> p2 = p1->GetSelf();
	p1 = nullptr;
	int k = 0;// <-- set breakpoint here
}

And this example shows how you can make derived classes return their own shared pointer:

//#include <memory>
//#include <iostream>
//using namespace std;

class Thing : public enable_shared_from_this<Thing>
{
public:
	~Thing();
	shared_ptr<Thing> GetSelf();
};

Thing::~Thing() {}// <-- set breakpoint here

shared_ptr<Thing> Thing::GetSelf()
{
	return shared_from_this();
}

class SubThing : public Thing
{
public:
	~SubThing();
	shared_ptr<SubThing> GetSelf();
};

SubThing::~SubThing() {}// <-- set breakpoint here

shared_ptr<SubThing> SubThing::GetSelf()
{
	return static_pointer_cast<SubThing>(shared_from_this());
}

int main(int argc, const char *argv[])
{
	SubThing* subthing = new SubThing;
	shared_ptr<Thing> p1 = shared_ptr<Thing>(subthing);
	shared_ptr<SubThing> p2 = subthing->GetSelf();
	p1 = nullptr;
	int k = 0;// <-- set breakpoint here
}

 

  • Upvote 1
Link to comment

Okay, I created a "SharedObject" class.  Everything that's not a simple math class should be derived from this:

class SharedObject : public Object, public enable_shared_from_this<SharedObject> {};

You can create a new class like this:

class MyClass : public SharedObject
{
	shared_ptr<MyClass> GetSelf();
};

And you can return the object itself safely like this:

shared_ptr<MyClass> MyClass::GetSelf()
{
	return static_pointer_cast<MyClass>(shared_from_this());
}

 

  • Upvote 1
Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...