Como declarar std :: unique_ptr e para que serve?

94

Procuro entender como std::unique_ptrfunciona e para isso encontrei este documento. O autor parte do seguinte exemplo:

#include <utility>  //declarations of unique_ptr
using std::unique_ptr;
// default construction
unique_ptr<int> up; //creates an empty object
// initialize with an argument
unique_ptr<int> uptr (new int(3));
double *pd= new double;
unique_ptr<double> uptr2 (pd);
// overloaded * and ->
*uptr2 = 23.5;
unique_ptr<std::string> ups (new std::string("hello"));
int len=ups->size();

O que é confuso para mim é que nesta linha

unique_ptr<int> uptr (new int(3));

Usamos inteiro como argumento (entre colchetes) e aqui

unique_ptr<double> uptr2 (pd);

usamos um ponteiro como argumento. Isso faz alguma diferença?

O que também não está claro para mim é como os ponteiros, declarados dessa forma, serão diferentes dos ponteiros declarados de forma "normal".

romano
fonte
13
new int(3)retorna um ponteiro para o novo int, assim como pdum ponteiro para o novo double.
David Schwartz

Respostas:

87

O construtor de unique_ptr<T>aceita um ponteiro bruto para um objeto do tipo T(portanto, ele aceita a T*).

No primeiro exemplo:

unique_ptr<int> uptr (new int(3));

O ponteiro é o resultado de uma newexpressão, enquanto no segundo exemplo:

unique_ptr<double> uptr2 (pd);

O ponteiro é armazenado na pdvariável.

Conceitualmente, nada muda (você está construindo um a unique_ptrpartir de um ponteiro bruto), mas a segunda abordagem é potencialmente mais perigosa, pois permitiria a você, por exemplo, fazer:

unique_ptr<double> uptr2 (pd);
// ...
unique_ptr<double> uptr3 (pd);

Assim, temos dois ponteiros únicos que efetivamente encapsulam o mesmo objeto (violando assim a semântica de um ponteiro único ).

É por isso que a primeira forma de criar um ponteiro exclusivo é melhor, quando possível. Observe que em C ++ 14 seremos capazes de fazer:

unique_ptr<int> p = make_unique<int>(42);

O que é mais claro e seguro. Agora, a respeito dessa sua dúvida:

O que também não está claro para mim é como os ponteiros, declarados dessa forma, serão diferentes dos ponteiros declarados de forma "normal".

Supõe-se que os ponteiros inteligentes modelem a propriedade do objeto e automaticamente cuidem da destruição do objeto apontado quando o último ponteiro (inteligente, proprietário) para esse objeto sair do escopo.

Desta forma, você não precisa se lembrar de fazer deleteem objetos alocados dinamicamente - o destruidor do ponteiro inteligente fará isso por você - nem se preocupar se você não cancelará a referência de um ponteiro (pendente) para um objeto que já foi destruído:

{
    unique_ptr<int> p = make_unique<int>(42);
    // Going out of scope...
}
// I did not leak my integer here! The destructor of unique_ptr called delete

Agora unique_ptré um ponteiro inteligente que modela a propriedade exclusiva, o que significa que a qualquer momento em seu programa deve haver apenas um ponteiro (proprietário) para o objeto apontado - é por isso que unique_ptrnão pode ser copiado.

Contanto que você use ponteiros inteligentes de uma maneira que não quebre o contrato implícito que eles exigem que você cumpra, você terá a garantia de que nenhuma memória será perdida e a política de propriedade adequada para seu objeto será aplicada. Ponteiros brutos não oferecem essa garantia.

Andy Prowl
fonte
3
Olá, não consegui perceber nada sobre model object ownershipo integer leakno código ou enforcing ownership policy for object. Você poderia sugerir tópicos / recursos para aprender esses conceitos?
Chama de udun
1
Não posso usar unique_ptr, sem obter um erro The text ">" is unexpected. It may be that this token was intended as a template argument list terminator but the name is not known to be a template.:, embora tenha #include <utility>e #include <memory>. Algum conselho?
Anônimo de
15

Não há diferença em trabalhar em ambos os conceitos de atribuição a unique_ptr.

int* intPtr = new int(3);
unique_ptr<int> uptr (intPtr);

é similar a

unique_ptr<int> uptr (new int(3));

Aqui unique_ptr exclui automaticamente o espaço ocupado por uptr.


como os ponteiros, declarados desta forma, serão diferentes dos ponteiros declarados de forma "normal".

Se você criar um inteiro no espaço de heap (usando nova palavra-chave ou malloc ), você terá que limpar essa memória sozinho (usando delete ou free respectivamente).

No código abaixo,

int* heapInt = new int(5);//initialize int in heap memory
.
.//use heapInt
.
delete heapInt;

Aqui, você terá que excluir heapInt, quando terminar de usar. Se não for excluído, ocorre vazamento de memória.

Para evitar esses vazamentos de memória, unique_ptr é usado, onde unique_ptr exclui automaticamente o espaço ocupado por heapInt quando ele sai do escopo. Portanto, você não precisa excluir ou liberar para unique_ptr.

fury.slay
fonte
10

Ponteiros exclusivos garantem a destruição do objeto que gerenciam quando saem do escopo. http://en.cppreference.com/w/cpp/memory/unique_ptr

Nesse caso:

unique_ptr<double> uptr2 (pd);

pdserá destruído quando uptr2sai do escopo. Isso facilita o gerenciamento de memória por exclusão automática.

O caso de unique_ptr<int> uptr (new int(3));não é diferente, exceto que o ponteiro bruto não é atribuído a nenhuma variável aqui.

fatihk
fonte
-1

De cppreference , um dos std::unique_ptrconstrutores é

Unique_ptr explícito (ponteiro p) noexcept;

Portanto, criar um novo std::unique_ptré passar um ponteiro para seu construtor.

unique_ptr<int> uptr (new int(3));

Ou é o mesmo que

int *int_ptr = new int(3);
std::unique_ptr<int> uptr (int_ptr);

A diferença é que você não precisa limpar depois de usá-lo. Se você não usar std::unique_ptr(ponteiro inteligente), você terá que excluí-lo assim

delete int_ptr;

quando você não precisar mais dele ou causará um vazamento de memória.

Tevada
fonte