Smart Pointers:
Issues with raw pointers
Types of smart pointers
Multiple Inheritance
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
#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;
}
#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;
}
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
#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;
}
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
#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;
}
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
#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;
}
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
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
#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;
}
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
#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;
}