SEP200

Smart Pointers and Multiple Inheritance

Summary

Smart Pointers:

    Issues with raw pointers

    Types of smart pointers

Multiple Inheritance

Issues with raw pointers

Overview

Pointers are frequently used to handle dynamic memory allocation

It is up to the developers to make sure that there are no memory leaks

Problems can arise when the program's execution does not work as expected

Memory Leak

#include <iostream>
#include <stdexcept>

class Resource
{
public:
    Resource()
    {
        std::cout << "Resource acquired" << std::endl;
    }
    ~Resource()
    {
        std::cout << "Resource released" << std::endl;
    }
    void use()
    {
        std::cout << "Resource in use" << std::endl;
    }
};

void functionThatThrows()
{
    throw std::runtime_error("An exception occurred");
}

void leakingFunction()
{
    Resource *res = new Resource();

    res->use();

    functionThatThrows();

    delete res;
}

int main()
{
    try
    {
        leakingFunction();
    }
    catch (const std::exception &exception)
    {
        std::cerr << "Caught exception: "
                  << exception.what() << std::endl;
    }

    return 0;
}

        

Fixing it

#include <iostream>
#include <memory>
#include <stdexcept>

class Resource
{
public:
    Resource()
    {
        std::cout << "Resource acquired" << std::endl;
    }
    ~Resource()
    {
        std::cout << "Resource released" << std::endl;
    }
    void use()
    {
        std::cout << "Resource in use" << std::endl;
    }
};

void functionThatThrows()
{
    throw std::runtime_error("An exception occurred");
}

void safeFunction()
{
    std::unique_ptr<Resource> res(new Resource());

    res->use();

    functionThatThrows();
}

int main()
{
    try
    {
        safeFunction();
    }
    catch (const std::exception &exception)
    {
        std::cerr << "Caught exception: "
                  << exception.what() << std::endl;
    }

    return 0;
}

        

Types of Smart pointers

Types

There are three types of smart pointers:

unique_ptr owns the object and no other smart pointers can point to it

shared_ptr owns the object but allows for multiple references

weak_ptr points to the object, but does not count

unique_ptr

#include <iostream>
#include <memory>

void create_array(int size)
{
    std::unique_ptr<int[]> array(new int[size]);
    
    for (int i = 0; i < size; i++)
    {
        array[i] = i;
    }
    std::cout << "created and array of size: "
              << size << std::endl;

    std::unique_ptr<int[]> new_array;
    //new_array = array; // illegal
    new_array = move(array);

    for (int i = 0; i < size; i++)
    {
        std::cout << new_array[i] << " ";
    }
    std::cout << std::endl;
}

int main()
{
    create_array(5);
    return 0;
}
      

Design Considerations

If an object should have a single owner, a std::unique_ptr is the best choice

You have to explicity call move to change ownership

If multiple parts of your program need to share ownership of something, a std::shared_ptr pointer can be used

A std::shared_ptr keeps track (count) of how many owners are there

shared_ptr

#include <iostream>
#include <memory>

void create_array(int size)
{
    std::shared_ptr<int[]> array(new int[size]);

    for (int i = 0; i < size; i++)
    {
        array[i] = i;
    }

    std::shared_ptr<int[]> new_array;
    new_array = array;

    std::cout << "created " << array.use_count()
              << " arrays of size: " << size << std::endl;
}

int main()
{
    create_array(5);
    return 0;
}
      

Design Considerations

A circular reference occurs when two or more objects reference each other directly or indirectly, creating a loop in the reference chain

This situation prevents the reference count of shared objects from reaching zero

Circular Reference

circular reference

#include <iostream>
#include <memory>

class B;

class A
{
public:
    std::shared_ptr<B> b_ptr;
    ~A()
    {
        std::cout << "A destroyed" << std::endl;
    }
};

class B
{
public:
    std::shared_ptr<A> a_ptr;
    // std::weak_ptr<A> a_ptr;
    ~B()
    {
        std::cout << "B destroyed" << std::endl;
    }
};

int main()
{
    std::shared_ptr<A> a(new A);
    std::shared_ptr<B> b(new B);

    a->b_ptr = b;
    b->a_ptr = a;

    return 0;
}

        

Multiple Inheritance

Introduction

A child class can be derived from more than one parent

This means that the child class inherits member variables and member functions from both parent classes

Access is still controlled via access specifiers: private, protected, and public

Multiple Inheritance

Multiple Inheritance

To add multiple base classes, the syntax is:

class Child: public Parent1, public Parent2, ...

Note that the constructors and destructors will be called in the provided order

Multiple Inheritance

Example

#include <iostream>
const int N = 64;

class Father
{
protected:
    char last_name[N] = "Vader";

public:
    Father() { std::cout << "Father class created"
                         << std::endl; }
    ~Father() { std::cout << "Father class destroyed"
                          << std::endl; }
};

class Mother
{
protected:
    char middle_name[N] = "Naberrie";

public:
    Mother() { std::cout << "Mother class created"
                         << std::endl; }
    ~Mother() { std::cout << "Mother class destroyed"
                          << std::endl; }
};

class Child : public Father, public Mother
{
public:
    Child() { std::cout << "Child class created"
                        << std::endl; }
    ~Child() { std::cout << "Child class destroyed"
                         << std::endl; }
    void name() { std::cout << "My full name is Luke "
                            << middle_name << " "
                            << last_name << std::endl; }
};

int main()
{
    Child Luke;
    Luke.name();

    return 0;
}

              

Diamond Inheritance

Imagine two classes inheriting the same base class

Also, imagine that another class inherits from these two classes

In this scenario, called a diamond problem, the first class in inherited twice!

This leads to ambiguity and for constructors and destructors to be called twice

Use virtual inheritance and namespaces to solve these issues

Diamond Inheritance

Example

#include <iostream>
const int N = 64;

class Grandfather
{
protected:
    int age = 80;

public:
    Grandfather() { std::cout << "Grandfather created"
                              << std::endl; }
    ~Grandfather() { std::cout << "Grandfather destroyed"
                               << std::endl; }
};

class Father : virtual public Grandfather
{
protected:
    char last_name[N] = "Vader";

public:
    Father() { std::cout << "Father class created"
                         << std::endl; }
    ~Father() { std::cout << "Father class destroyed"
                          << std::endl; }
};

class Mother : virtual public Grandfather
{
protected:
    char middle_name[N] = "Naberrie";

public:
    Mother() { std::cout << "Mother class created"
                         << std::endl; }
    ~Mother() { std::cout << "Mother class destroyed"
                          << std::endl; }
};

class Child : public Father, public Mother
{
public:
    Child() { std::cout << "Child class created"
                        << std::endl; }
    ~Child() { std::cout << "Child class destroyed"
                         << std::endl; }
    void name() { std::cout << "My full name is Luke "
                            << middle_name << " "
                            << last_name << std::endl; }
    void print() { std::cout << Mother::age
                             << Father::age; }
};

int main()
{
    Child Luke;
    Luke.name();

    std::cout << std::endl;

    return 0;
}

              

Suggested Reading

Smart pointers

Multiple inheritance