# 11.2 - Smart Pointers
Na seção 11, vimos que um ponteiro é um tipo que pode conter um endereço de memória.
Infelizmente quando se aprende ponteiros, a maioria dos cursos não mostra a forma correta de usá-los.
Como vimos na seção 11, podemos alocar um espaço de memória usando a keyword new e sempre que à usamos, precisamos usar a keyword delete para liberar tal espaço de memória.
#include <iostream>
int main()
{
/* Alocando 4 bytes de memória e salvando o endereço dessa memória na variável a */
int *a = new int;
/* De-referênciando o endereço que está salvo na variável a e atribuindo o valor 10 */
*a = 10;
/** De-referênciando o endereço que está salvo na variável a e,
* mostrando o valor que está salvo nesse endereço
*/
std::cout << *a << '\n; // 10
/* Liberando a memória que foi alocada */
delete a;
}
Existem casos onde essa é a forma correta de lidar com ponteiros, mas uma boa regra para se seguir é a seguinte:
Nunca use new e delete, sempre que possível, use smart pointers.
# O problema
Conforme a complexidade de um programa aumenta, memory leaks(Vazamentos de memória) começam a aparecer.
O que é memory leak? Veja a seguinte situação:
#include <iostream>
void memory_leak() {
/* Alocando 4 bytes de memória e salvando o endereço dessa memória na variável a */
int *a = new int;
/* De-referênciando o endereço que está salvo na variável a e atribuindo o valor 10 */
*a = 10;
/** De-referênciando o endereço que está salvo na variável a e,
* mostrando o valor que está salvo nesse endereço
*/
std::cout << *a << '\n; // 10
/**
* Aqui deveriamos usar a keyword delete para liberar a memória que foi alocada.
* Mas por algum motivo, esquecemos de fazer isso.
* Assim que a variável a sair do escopo da função, não teremos mais como acessá-la,
* sendo assim, não será possível liberar a memória.
*/
}
int main()
{
for(int i = 0; i < 1000; ++i){
memory_leak();
}
}
Isso é um memory leak, já percebeu que certos programas consumem mais e mais memória depois de certo tempo de execução? Esse pode ser um dos motivos.
# A solução
Felizmente, existem soluções para que você nunca tenha que se preocupar com a liberação de memória alocada.
Uma dessas soluções se chama Smart Pointers.
O que são:
Smart pointers são ponteiros, dentro de uma estrutura que é responsável por liberar a memória alocada assim que a variável sair do escopo.
Quem são:
std::unique_ptr
std::shared_ptr
std::weak_ptr
Para usá-los precisamos incluir o header <memory>
Usando std::unique_ptr
Syntax para criar um std::unique_ptr
Ao invés de usar new TIPO, chamamos a função std::make_unique<TIPO>() para criar um ponteiro.
#include <iostream>
#include <memory>
void no_memory_leak()
{
/**
* Da mesma forma que um ponteiro aloca 4 bytes de memória para o tipo int,
* um unique_ptr também irá alocar 4 bytes.
*/
std::unique_ptr<int> a = std::make_unique<int>();
/** Embora smart pointers sejam estruturas por volta de um ponteiro, podemos tratá-los
* como se fossem ponteiros normais, na maior parte dos casos.
* Uma das diferenças é que não precisamos deletá-los, pois,
* são deletados automaticamente assim que saem do escopo em que foram criados.
*/
*a = 10;
std::cout << *a << '\n'; // 10;
/* Smart pointers não precisam ser deletados manualmente */
}
int main()
{
for(int i = 0; i < 1000; ++i){
no_memory_leak();
}
}
Usando std::shared_ptr
Syntax para criar um std::shared_ptr
Ao invés de usar new TIPO, chamamos a função std::make_shared<TIPO>() para criar um ponteiro.
#include <iostream>
#include <memory>
void no_memory_leak()
{
/* Criando um std::shared_ptr do tipo float */
std::shared_ptr<int> a = std::shared<float>();
*a = 2.71828;
std::cout << *a << '\n'; // 2.71828;
/* Smart pointers não precisam ser deletados manualmente */
}
int main()
{
for(int i = 0; i < 1000; ++i){
no_memory_leak();
}
}
Usando std::weak_ptr
Syntax para criar um std::weak_ptr
Ao invés de usar new TIPO, chamamos a função std::make_weak<TIPO>() para criar um ponteiro.
#include <iostream>
#include <memory>
void no_memory_leak(std::weak_ptr<float> pointer)
{
/* Antes de de-referênciar um std::weak_ptr precisamos transforma-lo em um std::shared_ptr */
std::cout << *std::shared_ptr<float>(pointer) << '\n'; // 2.71828;
/* Smart pointers não precisam ser deletados manualmente */
}
int main()
{
std::shared_ptr<float> a = std::make_shared<float>(2.71828);
for (int i = 0; i < 1000; ++i)
{
no_memory_leak(a);
}
}
# Diferença entre std::unique_ptr, std::shared_ptr e std::weak_ptr
std::UNIQUE_ptr como o nome diz, é único.
Um unique_ptr não pode ser copiado.
#include <memory>
int main()
{
/* Criando um unique_ptr do tipo int com o valor 10 */
std::unique_ptr<int> a = std::make_unique<int>(10);
std::unique_ptr<int> b = a; // erro: call to deleted constructor of std::unique_ptr<int>
}
Devemos usar unique_ptr quando sabemos que uma entidade irá pertencer somente, e somente a uma outra entidade.
O unique_ptr é deletado assim que a entidade que o possui sair do escopo.
std::SHARED_ptr como o nome diz, pode ser compartilhado.
Ao contrário do unique_ptr, um shared_ptr pode ser copiado.
#include <memory>
int main()
{
/* Criando um shared_ptr do tipo float com o valor 1.30357 */
std::shared_ptr<float> a = std::make_shared<float>(1.30357);
std::shared_ptr<float> b = a; // sem erros
}
Devemos usar shared_ptr quando sabemos que uma entidade irá pertencer a mais de uma entidade ao mesmo tempo.
O shared_ptr é deletado assim que todas a entidades que o possuem sairem do escopo.
std::WEAK_ptr
Um weak_ptr funciona de forma parecida com o shared_ptr.
#include <memory>
int main()
{
/* Criando um shared_ptr do tipo int com o valor 10 */
std::shared_ptr<int> a = std::make_shared<int>(10);
/* Criando um weak_ptr que aponta pra um shared_ptr do tipo int */
std::weak_ptr<int> b = a;
/* Copiando um weak_ptr */
std::weak_ptr<int> c = b;
/* Transformando um weak_ptr em shared_ptr para poder de-referência-lo. */
std::cout << *std::shared_ptr<int>(c) << '\n';
}
Ao contrário do shared_ptr, um weak_ptr não implica que uma entidade possui um ponteiro, ele serve somente para o uso temporário de um shared_ptr, quando temos um algo que deve ser acessado somente se existir e não nos importamos se aquela entidade deixar de existir a qualquer momento.