C++ language

headshot image of me

Author: Daniele Aimo

Last update: Jul 8st, 2022

To share this page click on the buttons below;

Smart pointers

C++ does not have an automatic mechanism to manage dinamically allocated memory, in other words C++ does not have a garbage collector able to delete objects allocated in the heap which are no longer needed.

C++11 introduced Smart pointers to deal with automatic management of dinamically allocated objects.

A first version of smart pointer was introduced in C++98: that was auto_ptr. This first version had some limitations and so C++11 introduced a completed new set of smart pointers. I will not talk here of the auto_ptr since it has been superseded in the new version of the C++ standard (basically auto_ptr was similar to unique ptr but it lacks the possibility to be moved since move semantics has ben introduced in C++11).

Type of smart pointers

Smart pointers are regular C++ class template that wraps raw C++ pointers.

From C++11 there are 3 available smart pointers (plus, of course the auto_ptr which has been maintained for backward compatibility):

  • unique pointers
  • shared pointers
  • weak pointers

All smart pointers are declared in the header <memory>.

Unique pointers

Unique pointers are just an improvement of raw pointers to be used to handle dinamically allocated objects.

There are a few things to remember:

  • a unique pointer owns in exclusive way the resources is attached to (or, which is the same, points in exclusive way to a certain object)
  • a unique pointer automatically delete the resources is attached to when it goes out of scope
  • unique pointers cannot be copied, but only moved.

The third point descends from the first one: since a unique pointer points to a certain object in exclusive way, copy a unique pointer is forbidden (otherwise there will be 2 unique pointers that point to the same object). On the other hand move is possible because the ownership is transferred to the other pointer and the first one remains empty.

Use

To describe the simplest unique pointer use I will the CFile class. This class has nothing to do with unique pointer, it is just an example class that takes a filename as constructor parameter, if the file exists, it opens and read the file in binary form, putting the content in a buffer.

The reason I used this class is just that I wanted something that actually allocates memory (the buffer the file is put into) so that the destructor actually deletes something (plus the class does something that might even be useful, like check if the file exists and calculate the dimension of the file).

So the header file of the CFile class is:

#ifndef CFILE_H
#define CFILE_H

#include <sys/stat.h>
#include <iostream>
#include <string>
#include <fstream>
#include <stdexcept>

class CFile
{
    public:
        CFile(std::string file);
        static bool exists(std::string file);
        static size_t get_size(std::string file);
        static size_t get_size1(std::string file);
        static size_t get_size2(std::string file);
        virtual ~CFile();

    private:
        unsigned char *m_ptr_content;
        std::string m_filename;
        bool m_exist;
        size_t dim;

        size_t read_binary();
};

#endif // CFILE_H

and the implementation is the following

#include "CFile.h"
/*
 * 2 different ways to get the size of a file
 */
size_t CFile::get_size1(std::string file) {
    size_t rv = 0;
    std::ifstream is (file.c_str(), std::ifstream::binary);
    if (is) {
        // get length of file:
        is.seekg (0, is.end);
        rv = is.tellg();
        is.seekg (0, is.beg);
        is.close();
    }
    return rv;
}

size_t CFile::get_size2(std::string file) {
    size_t rv = 0;
    FILE *fp = fopen(file.c_str(),"rb");
    if(fp != NULL) {
        fseek(fp, 0L, SEEK_END);
        rv = ftell(fp);
        fseek(fp, 0L, SEEK_SET);
        fclose(fp);
    }
    return rv;
}

size_t CFile::read_binary() {
    size_t rv = 0;
    FILE *fp = fopen(m_filename.c_str(),"rb");
    memset(m_ptr_content, 0, dim);
    rv = fread(m_ptr_content,1,dim,fp);
    std::cout << "   Read " << rv << " bytes from files" << std::endl;
    fclose(fp);
    return rv;
}

/*
 * static function to verify if a file exists or not
 */
bool CFile::exists(std::string file){
    struct stat buffer;
    return (stat (file.c_str(), &buffer) == 0);
}

/*
 * - CONSTRUCTOR
 * it checks if the filename exists and if exists it reads
 * the content in binay form and put it into a buffer
 */

CFile::CFile(std::string file) {
    std::cout << "   CFile - Constructor (" << file << ")" << std::endl << std::endl;
    m_filename = file;
    m_exist = CFile::exists(m_filename);
    if(m_exist) {
        /* it reads the dimension of the file in 3 ways
           just for educational purpose */

        size_t size1 = get_size1(m_filename);
        size_t size2 = get_size2(m_filename);
        #ifdef PRINT_SIZE
        std::cout << "file size (1) " << size1 << std::endl;
        std::cout << "file size (2) " << size2 << std::endl;
        #endif // PRINT_SIZE
        if(size1 != size2) {
            throw std::length_error("Different file size calculated");
        }

        dim = size1;
        m_ptr_content = new unsigned char[dim];
        if(m_ptr_content != nullptr) {

            if(read_binary() != dim) {
                throw std::length_error("Unable to read all bytes");
            }
        }
    }
    else {
        m_ptr_content = nullptr;
        dim = 0;
    }
}

CFile::~CFile() {
    std::cout << "   CFile - DESTRUCTOR (" << m_filename << ")" << std::endl << std::endl;

    if(m_ptr_content != nullptr) {
        delete []m_ptr_content;
        m_ptr_content = nullptr;
        dim = 0;
    }
}

I will not say anything about that class since it is not related to smart pointers, I will only say that I put some output in strategic places (like constructor and destructor).

The program that uses unique pointer is really simple and demonstrates the main difference with raw pointer (please note that the example is incorrect since it does not release all the allocated memory, although this is wanted to demonstrate the differences).

#include <memory>
#include "CFile.h"

using namespace std;

int main(){
    CFile *rawPtrFile = new CFile("file1.bin");
    unique_ptr<CFile> ptrFile(new CFile("file2.bin"));
    cout << endl;
    cout << "   ---- Program exit ----" << endl;
    cout << endl;
    return 0;
}

Please note how to use the unique pointer: it's a template so it is necessary to specify which class the pointer is going to manage (CFile in this case) and it is a class so that it is necessary to use the constructor of that class with an argument (the new expression).

The output of this program will be something like that:

Output of a program that demonstrate unique pointers

As you see both the constructors are obviously called, but just the destructor of the unique pointer is called. That's it. This is the main advantage of using unique pointers. Using the unique pointer (and smart pointer in general) spares the need to delete the dynamically allocated objects handled through the smart pointer. So basically (to fix the previous example) it spares the need to write:

    delete rawPtrFile;

There very little you can do with a unique pointer. You cannot copy it:

   /* ERROR!!!! you cannot copy a unique ptr into another*/
   unique_ptr<CFile> ptr = ptrFile;

   vector<unique_ptr<CFile>> v;
   /* ERROR!!! push_back makes a copy */
   v.push_back(ptrFile); 

but you can move it:

   v.emplace_back(move(rawPtrFile));

and, in this case, rawPtrFile becomes empty and the object will be destroyed when the vector v goes out of scope.

Custom deallocator

It happens you want to do something automatically when an object is deleted, for this reason it is possible to specify a custom function to be called when the unique pointer is deleted.

See the example below:

using namespace std;

typedef  void (*delFunc)(CFile *);

void deleteCFile(CFile *ptr) {
    cout << "   CUSTOM DELETE FUNCTION" << endl;
    cout << "   Deleting CFile " << ptr->get_filename() << endl;
    delete ptr;
}

int main(){
    cout << endl;
    cout << endl;
    /* The custom deleter function type is used into the template 
       argument list and the actual deleter function is passed to 
       the constructor */
    unique_ptr<CFile, delFunc> ptrFile(new CFile("file2.bin"), deleteCFile);
    cout << endl;
    cout << "   ---- Program exit ----" << endl;
    cout << endl;

    return 0;
}

As you see in the comment we need to specify the type of the deleter function in the template argument list and to specify a custom function (of the same type) in the constructor of the unique pointer.

The result is that when the smart pointer is deleted the deleteCFile function is called, so that the output will be something like:

Output of a program that demonstrate unique pointers custom deleter function

There are alternative modern ways to specify the custom function. For example avoid typedef:

using FunctionPtr = void (*)(CFile *);
/* and define the pointer like this */
unique_ptr<CFile, FunctionPtr> ptrFile(new CFile("file2.bin"), deleteCFile);

or using lambda functions, with auto and decltype to even shorten the code:

auto delCFile = [](CFile *ptr) {
                    cout << "   CUSTOM DELETE FUNCTION" << endl;
                    cout << "   Deleting CFile " << ptr->get_filename() << endl;
                    delete ptr;
                };
unique_ptr<CFile, decltype(delCFile)> ptrFile(new CFile("file2.bin"), delCFile);

The output will be the same though.

Another way to initialize

Instead of using new inside the constructor of the unique pointer constructor there is another way to make a unique pointer (but, remember, this way is available only starting from C++14): use std::make_unique<>.

This allows to write code like that:

auto ptr(make_unique<CFile>("file1.bin"));

and avoid repetitions.

To share this page click on the buttons below;