Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Skills for using C++ smart pointers

4.65/5 (9 votes)
8 Oct 2013CPOL4 min read 25.3K   269  
I'd like to share my experience dealing with problems about memory allocation and memory leaks.

Introduction

C++ is well-known for efficiency. On the other hand, there are some traps which annoy developers. As you know, wild pointers and memory leaks are problems which happen to us most. Those problems make some developers desperate sometimes and makes using C++ difficult. Actually there are some methods to avoid those problems. Here, I'd like to share my experience dealing with those problems.  

Smart Pointers  

Most C++ developers know about smart pointers. We can use them with boost or C++ 11.  There are lots of articles that introduce smart pointers but less of those clarify how to use them right. Unfortunately there are some opinions to be ignored when using smart pointers. To some extent, that's right. Using smart pointers improperly may bring bad results, even memory leaks. So let's talk about the rules of using smart pointers. 

Generally there are three kinds of smart pointers: 

  • Scope Pointer: Scope pointer will auto release the object when the scope pointer exits a code statement. This is a so easy, one that we nearly will not make mistakes. So we will talk more about it. 
  • Shared pointer. Object will not be released until a non-shared pointer has the reference. This will be very useful to avoid wild pointers. 
  • Weak pointer. This must be used by combining with a shared pointer. Weak Pointer has a reference to a shared pointer. Weak pointer can be converted to a shared pointer. But if a shared pointer has released the object, the weak pointer will auto release the reference.  

Mgr class to use shared pointer

We like to use a class like xx_mgr to manage objects. There are some codes using the game server model as an example:  

C++
class palyer_t 
{ 
public:
    int id();
};
typedef shared_ptr<palyer_t> palyer_ptr_t;
class player_mgr_t
{
public:
    int add(palyer_ptr_t player_)
    {
        m_players[player_->id()] = player_;
        return 0;
    }
    palyer_ptr_t get(int id_)
    {
        map<int, palyer_ptr_t>::iterator it = m_players.find(id_);
        if (it != m_players.end())
        {
            return it->second;
        }
        return NULL;
    }
    int del(int id_)
    {
        m_players,erase(id_);
        return 0;
    }    
protected:
    map<int, palyer_ptr_t>    m_players;
}; 
 
  

player_mgr_t controls the life cycle of player_t. We must make sure only player_mgr_t owns the player_t shared pointer. We can use a temporary variable of the player_t shared pointer which will be auto released when the function exits. So when the player gets offline, we delete it from player_mgr_t so that this player_t object will be auto released. That's what we expect. 

Recourse object to use shared pointer

We are all familiar with the situation that we new an object or buffer to put contents read from a file or database. I recently solved a memory leak in my project that was caused by a MySQL query. The MySQL API of mysql_fetch_result returns an object that needs to free even though a zero row is included. For example: 

C++
struct db_data_t{
    strint data;
};
typedef shared_ptr<db_data_t> db_data_ptr_t;
db_data_ptr_t load_data(const string& sql_){
    //.....
    return new db_data_t();
}
int process_1(db_data_ptr_t data_);
int process_2(db_data_ptr_t data_);
int process_3(db_data_ptr_t data_);
int process(db_data_ptr_t data_){
    db_data_ptr_t data = load_data(sql);
    if (process_1(data)){
        return -1;
    }
    if (process_2(data)){
        return -1;
    }
    if (process_3(data)){
        return -1;
    }
}   

As you can see, whenever we quit the process, the object of db_data will be deconstructed. That makes sure db_data_t will not cause a memory leak. 

Property object to use shared pointer

This situation happens when two objects have the owner relationship. E.g., player_t may have a weapon, or may not. Once we allocate a weapon object to player_t, the weapon will not be destroyed until player_t is destroyed. Let's see an example code:

C++
class weapon_t;
typedef shared_ptr<weapon_t> weapon_ptr_t;
class palyer_t
{
public:
    int id();
    weapon_ptr_t get_weapon() { return m_weapon; }
    void set_weapon(weapon_ptr_t weapon_)  { m_weapon = weapon_; }
protected:
    weapon_ptr_t m_weapon;
}; 

Reference relationship to use weak pointer

I use weak pointers besides the situations mentioned above. Using a game server model is an example. Monster will auto attack players trying to approach it. So class monster_t has an interface lock_target() which will be auto invoked by the AI system. We should set the target player of the monster to null if the target player gets offline. That is annoying. That is where we regularly make mistakes. And weak pointer fits here very much.   

C++
class monster_t{
public:
    void set_target(shared_ptr<player_t> player_){
        m_target_player = player_;
    }
    shared_ptr<player_t> get_target() { return m_target_player.lock(); }
protected:
    weak_ptr<player_t>    m_target_player;
};    

m_target_player will be auto set to null if the real player gets offline. Do you remember the example of the mgr object? If we combine them together, it's easy to manage memory allocation.

Object Counter

Though we use smart pointers, we likely make mistakes that make program memory increase all the way. For example, we forget to delete player_t from player_mgr_t when the player gets offline in some situations; that will not generate a memory leak. Another example, our program will sometimes increase memory faster than at other times. But which object causes these memory allocations? That usually can't be tested in the developing environment. So we have a need to know object numbers anytime. Generally we need to dump the data of object numbers to a file as time goes on. Here is a simple implementation:

C++
#include <stdio.h>
#include "base/fftype.h"
using namespace ff;
class foo_t: public ffobject_count_t<foo_t>
{
};
class dumy_t: public foo_t, ffobject_count_t<dumy_t>
{
};
int main(int argc, char* argv[])
{
    foo_t foo;
    dumy_t dumy;
    map<string, long> data = singleton_t<obj_summary_t>::instance().get_all_obj_num();
    printf("foo_t=%ld, dumy_t=%ld\n", data["foo_t"], data["dumy_t"]);
    return 0;
}  

output:

foo_t=2, dumy_t=1 

I will upload the files of the implementation code. I just posted some key codes. First, ffobject_count_t will auto increase the number of objects during construction, and will auto decrease the number during destruction.

C++
class obj_counter_i
<pre>{
public:
    obj_counter_i():m_ref_count(0){}
    virtual ~ obj_counter_i(){}
    void inc(int n) { (void)__sync_add_and_fetch(&m_ref_count, n); }
    void dec(int n) { __sync_sub_and_fetch(&m_ref_count, n);        }
    long val() const{ return m_ref_count;                            }
    virtual const string& get_name() { static string ret; return ret; }
protected:
    volatile long m_ref_count;
}; 
template<typename T>
class obj_counter_t: public obj_counter_i
{
public:
    obj_counter_t()
    {
        singleton_t<obj_summary_t>::instance().reg(this);
    }
    virtual const string& get_name() { return TYPE_NAME(T); }
};
template<typename T>
class ffobject_count_t
{
public:
    ffobject_count_t()
    {
        singleton_t<obj_counter_t<T> >::instance().inc(1);
    }
    virtual ~ ffobject_count_t()
    {
        singleton_t<obj_counter_t<T> >::instance().dec(1);
    }
}; 

If we dump the data of object number to a file in a timely fashion, we will get such a file:

obj,num,20120606-17:01:41
dumy,1111
foo,222
obj,num,20120606-18:01:41
dumy,11311
foo,2422
obj,num,20120606-19:01:41
dumy,41111
foo,24442 

It's easy to write a tool to analyze the data. For example, we can use highchart (js lib) to draw lines. I have uploaded my implementation of the tool. The picture generated is like this:

Image 1

Summary

  • We should be careful with the C++ memory allocation.
  • Smart pointers help us deal with memory allocations more easily.
  • In my opinion, we should use Smart pointers always but properly.
  • We should know when to use a shared pointer and when to use a weak pointer. If you make mistakes, that makes the situation worse than the original pointer.
  • An object counter should be part of the infrastructure of our C++ program. That helps us analyze memory allocation as time goes.
  • More codes available here: Github: https://github.com/fanchy/RedRabbit/blob/gh-pages/fflib/base/fftype.h

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)