Crash Course in C++ for Cocos2d-x Developers (Part 1)

This is a crash course in programming in C++ for making games with Cocos2d-x. If you have never learned about C++ before and do not know anything about object oriented programming, then you might be better off buying an introductory book to C++.

If you had a C++ course 10 years ago and need a refresher so you can make games with Cocos2d-x using C++, then you are in the right place.

Either way, ask me as many questions as you want and I’ll do my best to help out.

This article is a prerequisite for a more advanced article titled Crash Course in C++ for Cocos2d-x Developers (Part 2).

The purpose of this article is to provide a basic introduction to C++ programming for Cocos2d-x C++ developers.

The topic discussed in this article include:

  • Cross Platform Portability
  • C++ Class Syntax
  • Automatic and Static Storage
  • Virtual Methods
  • Variables and the auto Keyword
  • Pointers and References
  • Logic Flow Control
  • Namespaces
  • Dynamic Memory Allocation with New and Delete
  • Operator Overloading
  • Smart Pointers

The C++ programming language is powerful, portable, flexible and performant. When it comes to making video games, C++ can be a great choice to enable productivity and expression of ideas.

Cross Platform Portability: iOS, Android, Windows Phone

We write code in the case sensitive C++ syntax and then use a compiler to build that code into a binary file that can run on a specific platform. By target platform I mean a computer architecture with a specific operating system.

Here are some common platforms we can target with Cocos2d-x:

  • iOS operating system running on iOS devices with the ARM based CPU
  • Android operating system running on Android devices with the ARM CPU
  • Windows Run Time running on a Windows Phone with the ARM CPU
  • Mac OSX operating system running on a Mac with an Intel CPU

Each of the target platforms requires a special build to be created by a compiler. So we can use Xcode to create iOS and Mac apps. We use the Android Development Tools combined with the Android Native Development Kit (NDK) to create Android apps, and Visual Studio to build Windows Run Time (WinRT) apps.

C++ Class Syntax

The C++ programming language is an object oriented programming language. This means that we write code in files where we define what we call classes.

Our programs/apps use class definitions to create binary code that represents the behavior and data defined in the class definitions.

An instance of a class is called an object.

It is sometimes confusing when we think about classes and objects and what the difference is.

A class is a definition(blueprint) of an object.

An object is an instance of a class in the memory of a computer that behaves as defined by its class definition.

A class is a text file that we write. We then compile the source code that contains our classes and the output is a binary file. That binary file/program can then be run on a compatible platform.

The binary program embodies the behavior/functionality that we defined when we created our classes and described how the classes may interact with each other.

These objects can encapsulate data and methods and provide a means to manipulate that data or execute methods through interfaces defined in class definitions. Interfaces are commonly defined in header .h files of a class definition.

The implementation of the class is defined in the .cpp files. More info on how this is done is described below.

Interfaces
A class interface defines how other objects in a program can interact with an instance of the class. Even a complicated class that has lots of functionality can have a very simple interface.

A good practice to following when defining interfaces is to make them very easy to use correctly and very difficult to use incorrectly. Part of this involves keeping them simple and intuitive.

Part of the interface usually needs to be created as public access, meaning that the public members of the class are accessible outside of the class. The default access control of members in a class interface is private. So we use the access control keywords public, private, or protected in the interface to define the access rules for members. More on class access control and inheritance access control will be provided below.

Members of a class include variables and methods.

Here is an example of a C++ class interface header file.

Note the use of // for comments. You can also use /* to start a multi-line comment and terminate that comment with */.

// Always use an include guard at the top of each header file. This is done with #pragma once.
#pragma once 
#include <string> // If you wanted to use std::string. Note the greater than/less than syntax for non-local project headers.
#include <vector> // If you wanted to use std::vector.
#include "HeyaldaPropertyDelegate.h" // An abstract base class inteface. More on this in the advanced C++ article linked to at the start of this article.
#include "HeyaldaProperty.h"
#include "SecretAI.h"
#include "cocos2d.h" // Include the cocos2d-x headers. Note the quote syntax for local project headers.

class MyClassName : public cocos2d::Node, public HeyaldaPropertyDelegate, private SecretAI
{
public:
     MyClassName();  // Constructor
     ~MyClassName()  // Destructor
     virtual void propertyUpdated(float value);
     static void doSomethingToTheClass();
private:
    void doSomeSetup();

    float               _myFloatVar;
    int                 _myIntVar;
    HeyaldaProperty     _heyaldaProperty;
};

The class keyword is used to start the interface definition.

You can then optionally add a colon followed by a comma separated list of classes to inherit from.

When a class inherits from some other class, the class becomes like that other class. It gets its methods and data declarations and implementations, but which which member functions and member variables are inherited can be limited with class access control and inheritance access control.

Class Access Control
Good class design in object oriented programming includes attempting to create simple interfaces and hide away much of the implementation. This makes debugging code and creating reusable modular code easier.

One mechanism that C++ provides for encapsulation are the access control keywords discussed briefly above. These are private, protected and public. These can be added in the class interface between the curly brackets as many times as needed. But I generally start with public on the top, then a protected section and add put private on the bottom.

class MyClasName 
{
friend class MyOtherClassName;

public:
    // Methods
    
    // Variables
    
protected:
    // Methods
    
    // Variables
    
private:
    // Methods
    
    // Variables
};

The public keyword makes class members accessible outside of the class. This is required for methods that we want other classes to access and is seldom used for variables.

The protected keyword makes the members only accessible from within the class or a subclass of the class. Friend classes can also access protected members.

The private keyword makes the members only accessible in the base class and are not accessible in subclasses. Friend classes can also access protected members.

Inheritance Access Control
In the example class heard file above, note the use of the public and private access control keyword in front of the classes that this class inherits from.

class MyClassName : public cocos2d::Node, public HeyaldaPropertyDelegate, private SecretAI

Using public, private, or protected inheritance here defines how classes that are aware of a MyClassName will see the public and protected members of the superclass that MyClassName inherits from.

Public, protected and private inheritance can be used as a tool to restrict access of public and protected members that are inherited from a superclass.

Public inheritance will keep access defined in the superclass the same as it is defined in the super class. e.g. private will stay private, protected stays protected and public stays public.

Protected inheritance will reduce access to all public members defined in the superclass down to protected,

Private inheritance will reduce all public and protected down to private.

But regardless of the type of inheritance access control chosen, we cannot open up access of members inherited from a superclass, e.g.. change private to protected or protect to public.

So in the example header file above, the public Node members (variables and methods)  will be publicly accessible to any object that can access the instance of MyClassName.

But the members inherited from SecretAI, that are defined public in SecretAI, will now be private members of MyClassName. The use of private before the SecretAI in the inheritance syntax above is how this can be defined.

Friend Classes
The friend keyword can be added inside of a class definition between the curly brackets like shown above. This enables the class that is defined in the friend declaration to have access to private and protected member functions and variables of the class.

Some say the friend feature is useful in large complex codebases, but it loosens access control and makes code less encapsulated. An instance of a friend class can bypass it’s friends’ interface and manipulate data in its friend. This effectively can make code more complex to understand, debug and support. So use the friend feature with caution.

For example, debugging a class that has friends means that you will likely also need to debug its friends at the same time. You might observe a private or protected member variables changing and need to dig through the friend classes to see which one is bypassing the classes interface and changing private or protected members. You might want to buy some extra aspirin for your medicine cabinet if you choose to frequently use the friend feature of C++.

Implementation .cpp Files
The implementation of methods is defined in the .cpp files. This is a convention that is commonly used.

The next code section below is an example of a .cpp class implementation.

Note that the method names defined in the interface all start with: MyClassName::

This is the difference between a function and a method. A method (also called a member function or a class method) is a part of a class. A C language function that does not have the class name and double colon before the function name is not part of a class. C functions are not discussed in this article. But know that you might see them in your C++ programs from time to time.

#include "MyClassName.h" // Tell this implementation which interface to use.

void MyClassName::doSomethingToTheClass()
{
    // Static methods can only access static variables of the class.
    // Static methods cannot access non-static class variables.
    // Static methods are commonly used for the factory method to create an instance of a class and return it.
}

MyClassName::MyClassName()
{
    // Constructor
    this->doSomeSetup();
}

MyClassName::~MyClassName()
{
    // Destructor
    _heyaldaProperty.setDelegate(nullptr);
}

void MyClassName::propertyUpdated(float value)
{
    // Do something with the updated value sent from the 
    _myFloatVar = value;
}

MyClassName::doSomeSetup()
{
    MyClassName::doSomethingToTheClass(); // This is how a static method is called.
    _heyaldaProperty.setDelegate(this); // The setDelegate is a custom method of HeyaldaProperty.
}

Automatic and Static Storage

The default data storage type in C++ is automatic. So when we define variables in our programs, the automatic storage type is applied.

This means that variables defined as class members will by default be unique to an instance of that class.

Local variables in member functions will only exist as temporary variables when they are declared in the member function and until the function completes execution.

Another commonly used storage is called static.

When the static keyword is put in front of a variable declaration, it instructs the compiler to only have one unique instance of that variable for all instances of that class.

The same is true for local temporary variables in member functions. If we use the keyword static in front of their declaration, then there will only be one instance of that variable for all instances of that class.

Virtual Methods

The virtual keyword is used with the method definition in a class to tell the compiler that the method might be overridden by a derived class and to prefer the overridden method of the derived class if a pointer of the the type of the parent class is used when calling the method.

But, if we do not use the virtual keyword in the parent class, then the compiler will create code that will call the method defined in the class that is of the same type as the type of the pointer that is calling the method. Using the virtual method is how we implement the object oriented programming concept polymorphism in C++.

In the following example, Mustang and Corvette are subclasses of Car. They both have a method called pressGasPedal and so does the parent class Car. The virtual keyword is applied to the pressGasPedal method in Car. With this code, the pointers at the end of the main function will call the pressGasPedal in the Corvette and Mustang classes. But if we remove the virtual keyword from the pressGasPedal method in Car, then the pressGasPedal method in Car would be called when using the pointers of type Car to access the object Mustang and Corvette objects.

#include 
#include "Test.hpp"

int main(int argc, const char * argv[]) {
    
    // Create an instance of Car.
    Car car = Car();
    
    // Create an instance of Mustang.
    Mustang  mustang = Mustang();
    
    // Create an instance of Corvette.
    Corvette  corvette = Corvette();
    
    // This will always call the method in the Mustang class.
    mustang.pressGasPedal();
    // This will always call the method in the Corvette class.
    corvette.pressGasPedal();
    
    // Create pointers to the mustang and corvette, but of the superclass type Car.
    Car * ptrMustang = & mustang;
    Car * ptrCorvette = & corvette;
    
    // If virtual is not used in the superclass Car for the pressGasPedal method,
    // then pressGasPedal of the class Car will be called.
    // But if virtual is used, then pressGasPedal of the derived class
    // will be called.
    ptrMustang->pressGasPedal();
    ptrCorvette->pressGasPedal();

    
    return 0;
}


#pragma once

#include 

class Car
{
public:
    Car(){}
    ~Car(){}
    virtual void pressGasPedal() { std::cout << "Car pressGasPedal" << std::endl;}
};

class Mustang : public Car
{
public:
    Mustang(){}
    ~Mustang(){}
    virtual void pressGasPedal() { std::cout << "Mustang pressGasPedal" << std::endl;}
};

class Corvette : public Car
{
public:
    Corvette(){}
    ~Corvette(){}
    virtual void pressGasPedal() { std::cout << "Corvette pressGasPedal" << std::endl;}
};

Variables

A variable is a name for a ‘piece of data’ that we want to use in our app. C++ is a a strongly typed language. When we declare a variable, we must also state what type it is.

Variables that we declare in the class definition (between the curly brackets in the .h file), cannot be initialized in the header file. They must instead be initialized in the class constructor or alternatively in the Cocos2d-x init method if you subclass a Cocos2d-x class.

int numberOfStars;   // This variable is not initialized. Its value likely will be some junk value.
int numberOfItems = 5; // This variable is initialized to 5.
auto nodeContainer = Node::create(); // The auto keyword is used here. 
Node * nodeContainer2 = Node::create(); // Instead of auto, we can be more explicit with the type declaration.

Types of data include the basic C types such as int, float, double, bool, and also includes other custom classes. So, depending on the type, a variable might just be raw data like a C type or structure, and it might also have functionality like methods in a class.

Don’t forget that C++ is an object oriented data-centric language. When we define classes we are essentially defining types. The C types included in C++ are special kinds of types because they are not classes.

Pointers and References

Variables, methods and functions can be accessed by their value, by a pointer or by reference.

Variables are a typed and named location in memory.

A pointer is used to point to the memory location that contains the value of a variable. Technically a pointer is also a variable, but its type is a pointer to a specific type of data.

// Declare and initialize a variable.
int numberOfFish = 100;  

// Declare and initialize a pointer to an int and initialize it to nullptr.
int * ptrNumberOfFish = nullptr;

// Set the pointer ptrNumberOfFish to point to the address of numberOfFish
// by using the address of operator.
ptrNumberOfFish = & numberOfFish;

// Access the value of a pointer by dereferencing the pointer.
int howManyFish = *ptrNumberOfFish; 

References are slightly different than pointers. They must be assigned a value when they are declared and they cannot be changed to reference another variable.

The following is a quick summary of C++ references.

int myVariable = 10; // Declares and assigns the variable named myVariable.

int & myReference = myVariable; // Declares and creates a reference to myVariable.

myReference = 5; // Set myVariable to be equal to 5.

myReference = 100; Sets myVariable to 100.

References are immutable. Once assigned on initialization, it will always refer to its reference.

The Dot and -> Operators
When working with variables, pointers and references, the syntax can vary regarding how to access public members of an object.

When we have a pointer to a variable, the members of that variable are accessible through that pointer by using the -> operator.

When we have a reference to a variable or a variable itself, the members of that variable are accessible via the . operator.

Here is an example.

// This may look strange, but here myString1 is calling a constructor of
// std::string and assigning it to myString1.
std::string myString1("Hello"); 

// This does the same thing as the above string declaration and initialization.
std::string myString2 = "Hello"; 

// Note that since we have a variable std::string, that we use the dot syntax 
// to access its member functions.
size_t length = myString.length();

// References also use the dot syntax.
std::string & myStringRef = myString2;
size_t length = myStringRef.length();

// Here car is a pointer to a Corvette variable, so we use the -> syntax
// to access its members.
Corvette * car = Corvette();
car->pressGasPedal();

Cocos2d-x Memory Management

I have not used smart pointers when working with Cocos2d-x for the following reasons.

  1. Cocos2d-x has its own memory management scheme as a part of what is called a scene graph.
  2. Creating shared pointers from the raw pointers returned by the Cocos2d-x factory creation methods of Node based classes would result in bad access crashes due to inaccurate shared pointer reference counting (Cocos2d-x parent Node objects will also effectively call delete on their children).
  3. It is not possible to create a unique pointer from the raw pointer returned by the Cocos2d-x factory methods for creating Node based objects (and why would we want to do that anyway).
  4. We cannot create weak pointers because we would first need to create a shared pointer.

Cocos2d-x’s Memory Management Scheme
During runtime in a Cocos2d-x game, the scene graph is an upside down tree (like a family tree) starting with a scene as the highest level object in the inverted tree. As the game world is constructed, nodes and other node based objects are added as children to the running scene or to children of the running scene.

The base class for almost all Cocos2d-x objects is Node. Node provides a feature called addChild. So we start with an instance of the Scene class and add children to it using addChild. And those children can have children. The parent of each child is responsible for cleaning up the memory of its children when it is destroyed. And the Cocos2d-x Director singleton is used to destroy the scene.

Shared pointers would normally be created when the dynamic memory is allocated via a call to the keyword new. But the factory methods of cocos2d-x Node based objects, called create, encapsulates the dynamic memory call to new and returns a raw pointer. Technically we could create a shared pointer from that raw pointer, but that would cause problems because Cocos2d-x will delete the Node based object for us later. The shared pointer would also try to delete it, resulting in a bad access crash.

So the factory creation methods for Cocos2d-x objects will return a raw pointer to an object. We then use the addChild method to add that newly created object to a parent node. Cocos2d-x will use the screen graph to cleanup memory when the scene is destroyed. So the benefit of using smart pointers with Cocos2d-x is diminished.

The Auto Keyword

The auto keyword can be used in place of spelling out a specific type in our program. This is useful when the type contains a lot of text to spell out. To use the auto keyword, the variable must be initialized and the type must be able to be inferred from the assignment. Once the auto variable is initialized, its type cannot be changed and it behaves as if you would have spelled out the type name.

It is important to note that the auto keyword can be used to hold any data type, including variables, pointers, and references. The auto keyword just tells the compiler to infer the type from the variables initialization.

We cannot use auto in the class header file for member variables, since we cannot initialize member variables in the class header.

Logic Flow Control

Conditionals
In C++, we have the following conditionals to use to test objects. These are also operators that can be overloaded by custom classes.

It is a good practice to use parentheses to make the precedence of these operators explicit when we use more than one of these in the same line of code.

These conditional operators are commonly used to determine the flow of the program.

==  Equal

!=   Not Equal

<    Less Than

>    Greater Than

<= Less Than or Equal To

>= Greater Than Or Equal T0

Flow Control
The most common logic control used in C++ programs are probably if statements, switch statements, for loops, while loops and do while loops.

The If Statement
The if statement is used to conditionally determine the flow of the code.
The conditional is the code that is between the parenthesis of the if statement.

bool testA = true;
if (testA)
{
    // do something because testA is true.
}

The Switch Statement
The switch statement, like the if statement, is used to conditionally determine the flow of code.
It takes an integer value, an enumeration, or a class enumeration as an argument and executes the case that is true.
In the following example the case 2 would be executed.

Note that case 1 has brackets surrounding the “do something”. Use brackets like this for each case inside of the switch statement if you want to initialize local variables inside of the switch statement. Otherwise you will get a compiler error.

int testInteger = 2;

switch (testInteger) {
    case 1:{
        // Do something
        }
        break;
    case 2:
        // Do something
            
        break;
    case 3:
        // Do something
            
        break;
        
    default:
        // Do something
        break;
}

The For Loop
The for loop is one way in C++ to iterate, or do something repeatedly.
The first example below shows a loop that will execute 10 times.
The second example takes a std::vector of some object and then iterates over that vector and calls the doSomething method on each element in the vector.

for (int i=0; i<10; i++)
{
    // Do something
}

for (auto element : vectorOfElements)
{
    element.doSomething();
}

The While Loop
The while loop is another way in C++ to iterate, or do something repeatedly, as long as a condition is true.

int count = 0;
while (++count < 10)
{
    // Repeats until count is equal to 10.    
}

The Do While Loop
The do while loop is used to do something once and then continue to do it repeatedly while a condition is true.

bool testA = false;
do 
{
    // Code in this do while loop will execute only once, 
    // since test is equal to false.  
} while (testA);

Namespaces and The Using Directive

The namespace keyword enables wrapping class interfaces and implementations. This is done to deal with the possibility that there could be two classes with the same name.

For example, you might want to create a string class. But there already is a std::string class. The std is the namespace of the standard library.

namespace MyStandardCode
{
    class string
    {
    public:
        void doSomething();
    };
}

The code in this namespace would then later be used by including the file(s) that the namespace wraps and by adding the using directive to the top of the .cpp file like this.

using MyStandardCode;

...

// Inside of a method I could use the string like this.
MyStandardCode::string aString;
aString.doSomething();

// But since I added the using directive to the top of this class,
// I could alternatively do the following.
// But I would run into a naming collision if I also added the using std;
// to the class. This would result in a build error because it would be 
// ambiguous as to which string class to use.
string aString;
aString.doSomething();

New and Delete

An instance of a class can be created using the new keyword.

// Allocates memory for a MyCustomClass object and calls 
// the MyCustomClass constructor.
MyCustomClass * myClass = new MyCustomClass(); 

// Calls the MyCustomClass destructor and then frees the memory
// used by the MyCustomClass object.
delete myClass;

Less Important Cocos2d-x C++ Developer Topics

The remainder of this article includes topics that are good to know and while are important topics to programming in C++, it is debatable how important they are to a Cocos2d-x C++ developer.

You can continue reading here, or jump ahead and return to this content when you have more time to soak up information about C++ programming that is not completely necessary for making games with Cocos2d-x in C++.

Continue to Crash Course in C++ for Cocos2d-x Developers (Part 2)

Stack and Heap

The heap is a block of memory that is made available to the program for use for the duration of the program. When we use the new keyword to create an object, we are allocating memory on the heap.

The stack is what is called a LIFO (last on first out) stack. When we create objects inside of methods without using the keyword new, then those objects are created on the stack. When the method is done executing, the memory is automatically freed and is no longer accessible.

When we create variables of simple C types (int, float, double, or bool) in the interface of class, those variables are created on the heap and are available for the life of the class.

It is also possible to declare custom classes in the interface that are not pointers or references.
For example, if we declare a variable of a class type ScriptParser in the interface of a class called GameScene, the instance of the class ScriptParser is created on the heap and is available for the life of the class GameScene.

The default constructor is called on the custom class ScriptParser when the class GameScene is created.

Note that due to Cocos2d-x’s memory management scheme described above, it is not possible to create Node based subclasses using this technique.

#include "cocos2d.h"
#include "ScriptParser.h"

class GamePlayScene : public cocos2d::Layer 
{
public:
    ...

private:
    ScriptParser  _scriptParser;
};

Operator Overloading

Operator overloading is a powerful feature that enables us to define how our custom types e.g. classes, will behave when an operator is used. Examples of operators are +, -, *, /, %, and many others,but the listed are probably the most commonly overloaded operators.

With basic C types, such as int, we can add two numbers together.

int numberA = 5;

int numberB = 5;

int numberC = numberA + number B;

But what about a two dimensional vector defined by the Vec2 class used in Cocos2d-x. What if we wanted to add two vectors together?  Can we use the + operator, just like we can with int or float? The answer is yes, but only because operator overloading was used by the Vec2 class to implement that capability.

cocos2d::Vec2 vectorA = Vec2(5,5);
cocos2d::Vec2 vectorB = Vec2(5,5);
cocos2d::Vec2 vectorC = vectorA + vectorB; 
// The result is vectorC = Vec2(10,10)

The following code is similar to what Vec2 does to implement operator overloading, but has been simplified for clarity.

The declaration of the + operator overload is shown here.

Vec2& operator+=(Vec2& v);

The definition / implementation of the operator overload is shown here.

Vec2 & Vec2::operator+(Vec2& v)
{
    Vec2 result(*this);
    result.x += v.x;
    result.y += v.y;
    return result;
}

The operator keyword combined with the operator is how we implement the overloading of operators.

The important thing to take away from this example is that we can define how the overloadable operators behave for our custom classes. Wikipedia has a good list of C++ overloadable operators.

Note the similarity between a member function and the operator overload definition and declaration. They are virtually the same pattern with a return type, the class type, double colon,  and then end with a parameter defined in parentheses. The only difference is that instead of a function name, we use the keyword operator followed by the operator symbol we want to overload.

The parameter passed to the operator overload, the Vec2& v, is effectively the right hand side of the operator. e.g. vectorA + vectorB ,where vectorB is on the right hand side.

So for the above example of adding vectorA and vectorB together, the this in the method definition of the operator overload is VectorA. The vector passed by reference into the member function, Vec2 & v, is VectorB.

A temporary variable result is created and initialize with the copy constructor of Vec2 with the values of vectorA. Then vectorB is added to the temporary variable result. And finally, a reference to the temporary vector result is returned.

Smart Pointers

C++11 added new types of pointers called Smart Pointers to the C++ programming language. But for reasons described in the Pointers and References section above, these are not used much in Cocos2d-x. But here is an overview because that might change in the future.

Smart pointers help with memory management and can be used to create more robust code.

Unique Pointer
A unique pointer cannot be copied and there can only be one pointer to the object. The unique pointer is created when the object is created.

// Create an instance of MyClassName with a unique pointer called myPtr.
std::unique_ptr<MyClassName> myPtr(new MyClassName());

Shared Pointer
Shared pointers allow the pointer to be copied. Each time the pointer is copied, a reference count is incremented.
When all shared pointers that point to an object are deleted or go out of scope, then the object is also deleted.

So shared pointers help with memory management.

Shared pointers can be passed to methods by reference.

// Create an instance of std::string and a shard pointer called myPtr.
auto myPtr1 = std::make_shared<std::string>("hello");

// The following shows how the reference count is incremented and decremented.
printf("myPtr1.use_count():%ld\n", myPtr1.use_count());
auto myPtr2 = myPtr1;
printf("myPtr1.use_count():%ld\n", myPtr1.use_count());
auto myPtr3 = myPtr1;
printf("myPtr1.use_count():%ld\n", myPtr1.use_count());
auto myPtr4 = myPtr1;
printf("myPtr1.use_count():%ld\n", myPtr1.use_count());
myPtr2 = nullptr;
printf("myPtr1.use_count():%ld\n", myPtr1.use_count());
myPtr3 = nullptr;
printf("myPtr1.use_count():%ld\n", myPtr1.use_count());
myPtr4 = nullptr;
printf("myPtr1.use_count():%ld\n", myPtr1.use_count());
myPtr1 = nullptr;
printf("myPtr1.use_count():%ld\n", myPtr1.use_count());

Debug Log Output
myPtr1.use_count():1
myPtr1.use_count():2
myPtr1.use_count():3
myPtr1.use_count():4
myPtr1.use_count():3
myPtr1.use_count():2
myPtr1.use_count():1
myPtr1.use_count():0

Weak Pointer
The weak pointer does not impact the shared pointer reference count.

To create a weak pointer, first create a shared pointer. To use the weak pointer it is necessary to first get a lock on the weak pointer. Getting the lock returns a shared pointer that we can then use.

When the shared pointers reference count becomes zero, the weak pointer is also destroyed.

The weak pointer is useful to avoid a circular reference. One way that a circular reference can occur is with the delegation design pattern.

For example, a class RaceCar has a member variable named GasPedal. GasPedal requires a delegate to process it’s gas pedal state. RaceCar assigns itself to be the delegate of GasPedal. When RaceCar calls setDelegate on GasPedal, if the pointer used in GasPedal were a shared pointer, then there would be a circular reference. If GasPedal were only destroyed when RaceCar is destroyed, then he reference count of RaceCar could never reach zero because GasPedal would have incremented the use_count of RaceCar.

To avoid the circular reference issue, pass a reference of the shared pointer to the GasPedal’s setDelegate method, and then make a weak pointer from the shared pointer inside of GasPedal’s setDelegate method.

auto myPtr1 = std::make_shared<std::string>("hello");
auto weakPtr = std::weak_ptr<std::string>(myPtr1);

// To use a weak pointer, we must first get a lock on the pointer.
auto ptr = weakPtr.lock();
if (ptr)
{
    // The pointer is valid, so we can use it.
}

Continue to Crash Course in C++ for Cocos2d-x Developers (Part 2)

About the author

Jim Range

Jim Range has over a decade of experience architecting and implementing software based solutions for numerous fortune 500 companies in industries such as financial services, retail, and insurance, as well as small businesses. Jim has an excellent understanding of the software development lifecycle, agile development, and how to manage the development of an app. Over the past ten years Jim has been focused on mobile software development. He has created over 138 apps that have been downloaded more than 10 million times from the Apple App Store, Google Play, Amazon.com and Windows Phone Store. Jim also has experience as an information security consultant where he has provided numerous network and web application security assessments for Fortune 500 companies in the financial services, retail and insurance industries.

Comments are closed