Smart Pointers
C++ 11 standard solved a major safety problem with the language: Managing memory
- Creating memory
- Deleting memory
- Managing who owns memory and how many owners are there?
Example
Problematic code
Memory becomes difficult to manage when you give the pointers or references away. Here is an example.
// Interface for an internet socket API
class socket_i {
public:
virtual void send() = 0;
virtual void recv() = 0;
virtual ~socket_i() {}
};
// Child class that implements an interface
class socket : public socket_i {
public:
void send() override {
}
void recv() override {
}
};
// An HTTP library that uses a socket interface to communicate
class http {
public:
http(socket_i *sock) : m_socket(sock) {
}
private:
socket_i *m_socket;
};
// Multiple problems with this code
http* create_http() {
socket s;
http http_obj(&s);
return http_obj;
}
int main(void) {
http* http_obj = create_http();
}
One of the problems can be simplified to this:
int* get() {
int x;
return &x;
}
void problem() {
int* pointer = get();
*pointer = 123;
}
Fixed Version 1
The partially corrected version creates a new memory reference, and it leaves it up to the consumer of the code to actually delete the object. This is just one of the issues: memory management problem. Because you are relying on the user of the code to delete your memory, it creates traps in your code.
// Let us partially fix our code
http* create_http() {
socket s;
// Let us create new memory which will not go out of scope
// even when this function exits
// BUT:
// We have now created memory management problem:
// ie:
// - Who deletes this memory?
// - When do they do it?
// - Can you ensure that they delete it?
http * http_obj = new http(&s);
return http_obj;
}
int main(void) {
http* http_obj = create_http();
// We better delete our object or else it is memory leak
delete http_obj;
}
std::unique_ptr
Let us introduce the concept of using a unique pointer such that we do not have to worry about deleting it.
// Let us partially fix our code
std::unique_ptr<http> create_http() {
socket s;
std::unique_ptr<http> http_obj = std::make_unique<http>(&s);
return http_obj;
}
int main(void) {
std::unique_ptr<http> http_obj = create_http();
// No need to worry about deleting our allocated resource
//delete http_obj
}
The unique_ptr is also special because it will force you to maintain "one owner".
std::shared_ptr
If we continue in our example, we will notice that there is still a fundamental flaw which is that the socket that was created would go out of scope, and the http class would then end up using a reference to memory that no longer is alive (no longer in scope).
// Interface for an internet socket API
class socket_i {
public:
virtual void send() = 0;
virtual void recv() = 0;
virtual ~socket_i() {}
};
// Child class that implements an interface
class socket : public socket_i {
public:
void send() override {
}
void recv() override {
}
};
// An HTTP library that uses a socket interface to communicate
class http {
public:
http(std::shared_ptr<socket_i> sock) : m_socket(sock) {
}
private:
std::shared_ptr<socket_i> m_socket;
};
Cleaned up code
#include <iostream>
#include <memory>
// Interface for an internet socket API
class socket_i {
public:
virtual bool open(const std::string& hostname) = 0;
virtual void send(const std::string& data_to_transit) = 0;
virtual std::string recv() = 0;
virtual ~socket_i() {}
};
// An HTTP library that uses a socket interface to communicate
class http {
public:
http(std::shared_ptr<socket_i> sock) : m_socket(sock) {
}
~http() {
std::cout << "Destructor of http class has been called" << std::endl;
}
void send_request(const std::string& host, const std::string& resource) {
std::string request = "GET " + resource + " HTTP/1.1\r\n";
request += "Host: " + host + "\r\n";
request += "Connection: close\r\n\r\n";
m_socket->send(request);
std::string response = m_socket->recv();
std::cout << "Response:\n" << response << std::endl;
}
private:
std::shared_ptr<socket_i> m_socket;
};
class linux_socket : public socket_i {
public:
bool open(const std::string& hostname) override {
return false;
}
void send(const std::string& data_to_transit) override {
}
std::string recv() override {
return std::string{};
}
};
// Let us partially fix our code
std::unique_ptr<http> create_http() {
auto socket = std::make_shared<linux_socket>();
// References to the shared pointer:
std::cout << "reference count before: " << socket.use_count() << std::endl;
// Let us create new memory which will not go out of scope
// even when this function exits
// BUT:
// We have now created memory management problem:
// ie:
// - Who deletes this memory?
// - When do they do it?
// - Can you ensure that they delete it?
std::unique_ptr<http> http_obj = std::make_unique<http>(socket);
std::cout << "reference count after: " << socket.use_count() << std::endl;
return http_obj;
}
int main(void) {
auto http_obj = create_http();
// This uses "socket" which was created at the first line inside create_http()
http_obj->send_request("google.com", "index.html");
// We better delete our object or else it is memory leak
//delete http_obj;
std::cout << "End of main()" << std::endl;
}