Skip to content

Instantly share code, notes, and snippets.

@rolandoam
Created November 19, 2012 04:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rolandoam/4109020 to your computer and use it in GitHub Desktop.
Save rolandoam/4109020 to your computer and use it in GitHub Desktop.
Conversion from retain/release to smart pointers on C++

About this doc

This is a living doc and is intended to be modified!

Conversion plan

The idea of this doc is to set a minimal conversion guide to pass from the old retain - release - autorelease in cocos2d-x to a more modern, stable and cross-platform shared_ptr, auto_ptr or weak_ptr. The latter will be more friendly to the developer and also similar to what ARC provides in Objective-C

Simple case

Consider this simple case, using the old retain-release methodology.

class Simple : CCObject
{
	int num;
public:
	Simple(int n) : CCObject() {
		num = n;
		cout << "Simple(" << num << ")" << endl;
	}

	~Simple() {
		cout << "~Simple(" << num << ")" << endl;
	}

	void foo() {
		cout << "foo " << num << endl;
	}
};

void someFunction()
{
	Simple a = new Simple(1);
	a->foo();
}

This first case will leak a because you're creating it, and not deleting the object. The old way to overcome this, was to use autorelease.

...
	Simple a = new Simple(1);
	a->autorelease();
	a->foo();

Or a similar construct (usually a static create method or similar). The new proposed way to do this is to either use auto_ptr or just delete the object at the end. The advantage of using auto_ptr, is that it will automatically manage the deletion once the auto_ptr gets out of the scope.

void someFunction()
{
	auto_ptr<Simple> a = new Simple(1);
	a->foo();
}
// at this point, the instance of Simple associated with a is deleted.

Another benefit, is that auto_ptr also overloads the dereference operator, so you can access methods from the internal pointer using the well known -> operator.

Once you have this, you can remove the CCObject inheritance (if we're not using it for anything else).

Reference counting

The case before was too simplistic. Let's consider another one a little more complex, where you share pointers and want to release the memory only when you're sure you have no more references to that object.

#include <string>
#include <iostream>
#include <memory>
#include <vector>

using namespace std;

class CCNode;

typedef shared_ptr<CCNode> s_node;

class CCNode
{
	int tag;
	CCNode* parent;
	vector<s_node> children;
public:
	CCNode(int n) {
		tag = n;
		cout << "CCNode(" << tag << ")" << endl;
	}

	~CCNode() {
		cout << "~CCNode(" << tag << ")" << endl;
	}

	void foo() {
		cout << "foo " << tag << endl;
	}

	void addChild(s_node& n) {
		children.push_back(n);
		n->parent = this;
	}
};

int main()
{
	s_node parent(new CCNode(0));
	for (int i=1; i <= 10; i++) {
		s_node n(new CCNode(i));
		parent->addChild(n);
	}

	return 0;
}

NOTE: Please disregard the name of the typedef (s_node, we can rename it later to something more interesting).

If you run this code, you will see that the constructor for the whole 11 instances is called sequentially, and then the destructor for the first object is called, followed by the 10 children, which is the expected behaviour. In this case, to avoid a circular reference problem, the parent pointer is a plain pointer. If a child has the need to use the parent pointer, it should check to see if it's non-NULL.

The important point here is that we're passing the shared_ptr as reference to the call for addChild, this is the recommended way to do it.

When to use shared_ptr vs weak_ptr vs auto_ptr

First of all, you should avoid auto_ptr, unless you're returning an object that will be own by the caller.

The basic rule of thumb is:

  • shared_ptr: the caller and the callee share ownership of the object. The classic example for us would be CCNode <-> children, mainly because by design, the CCNode holds the scene-graphs, and must ensure that the children are owned by the parent.
  • weak_ptr: the callee owns the memory, the caller only needs a reference. An example of this would be to return something created in the API internally and used by the developer (need cc2d example here).
  • auto_ptr: try to avoid it. This might be useful for returning strings when you want to pass ownership to the caller. But most of the cases, it should be avoided.

Also, make sure if you receive smart pointers, they're are received and passed by reference, this for performance reasons1

Using c++0x vs c++11

Since the only thing we need are smart pointers, which are included in most c++0x implementations, ideally we want to have some sort of typedef to match our pointers, basically because for c++0x the smart pointers are located in the namespace tr1, where in c++11 are in the std namespace:

// c++0x
#include <tr1/memory>

std::tr1::shared_ptr<CCNode> ptr;

// c++11
#include <memory>
std::shared_ptr<CCNode> ptr;

We can easily overcome this using a simple typedef and an #ifdef or something similar to test for the c++ version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment