shared_ptr para uma matriz: deve ser usada?

172

Apenas uma pequena consulta sobre shared_ptr.

É uma boa prática usar shared_ptrapontar para uma matriz? Por exemplo,

shared_ptr<int> sp(new int[10]);

Se não, então por que não? Uma razão pela qual eu já estou ciente é que não é possível aumentar / diminuir o shared_ptr. Portanto, ele não pode ser usado como um ponteiro normal para uma matriz.

tshah06
fonte
2
FWIT, você também pode considerar usar std::vector. Você precisaria ter cuidado para passar a matriz usando referências para não fazer cópias dela. A sintaxe para acessar dados é mais limpa que shared_ptr, e redimensioná-la é muito, muito fácil. E você obtém toda a bondade do STL, se quiser.
Nicu Stiurca
6
Se o tamanho da matriz for determinado em tempo de compilação, você também pode considerar o uso std::array. É quase o mesmo que uma matriz bruta, mas com semântica adequada para uso na maioria dos componentes da biblioteca. Especialmente objetos desse tipo são destruídos com delete, não delete[]. E vector, diferentemente , ele armazena os dados diretamente no objeto, para que você não obtenha alocação extra.
Celtschk

Respostas:

268

Com o C ++ 17 , shared_ptrpode ser usado para gerenciar uma matriz alocada dinamicamente. O shared_ptrargumento do modelo neste caso deve ser T[N]ou T[]. Então você pode escrever

shared_ptr<int[]> sp(new int[10]);

No n4659, [util.smartptr.shared.const]

  template<class Y> explicit shared_ptr(Y* p);

Requer: Y deve ser do tipo completo. A expressão delete[] pquando Té um tipo de matriz ou delete pquando Tnão é um tipo de matriz deve ter um comportamento bem definido e não deve gerar exceções.
...
Observações: Quando Té um tipo de matriz, este construtor não devem participar na resolução de sobrecarga, a menos que a expressão delete[] pseja bem formado e ou Té U[N]e Y(*)[N]é convertível em T*, ou Té U[]e Y(*)[]é convertível para T*. ...

Para suportar isso, o tipo de membro element_typeagora é definido como

using element_type = remove_extent_t<T>;

Elementos da matriz podem ser acessados ​​usando operator[]

  element_type& operator[](ptrdiff_t i) const;

Exige: get() != 0 && i >= 0 . Se Té U[N], i < N. ...
Comentários: Quando Tnão é um tipo de matriz, não é especificado se esta função de membro é declarada. Se for declarado, não é especificado qual é o seu tipo de retorno, exceto que a declaração (embora não necessariamente a definição) da função seja bem formada.


Antes do C ++ 17 , nãoshared_ptr podia ser usado para gerenciar matrizes alocadas dinamicamente. Por padrão, chamará o objeto gerenciado quando não houver mais referências a ele. No entanto, quando você aloca usando, precisa chamar , e não , para liberar o recurso.shared_ptrdeletenew[]delete[]delete

Para usar corretamente shared_ptrcom uma matriz, você deve fornecer um deleter personalizado.

template< typename T >
struct array_deleter
{
  void operator ()( T const * p)
  { 
    delete[] p; 
  }
};

Crie o shared_ptr da seguinte maneira:

std::shared_ptr<int> sp(new int[10], array_deleter<int>());

Agora shared_ptrchamará corretamente delete[]ao destruir o objeto gerenciado.

O deleter personalizado acima pode ser substituído por

  • a std::default_deleteespecialização parcial para tipos de matriz

    std::shared_ptr<int> sp(new int[10], std::default_delete<int[]>());
  • uma expressão lambda

    std::shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });

Além disso, a menos que você realmente precise compartilhar a participação do objeto gerenciado, a unique_ptré mais adequado para esta tarefa, pois possui uma especialização parcial para os tipos de matriz.

std::unique_ptr<int[]> up(new int[10]); // this will correctly call delete[]

Alterações introduzidas pelas extensões C ++ para fundamentos de biblioteca

Outra alternativa pré-C ++ 17 às listadas acima foi fornecida pela Especificação Técnica dos Fundamentos da Biblioteca , que foi aprimorada shared_ptrpara permitir que ela funcione imediatamente para os casos em que possui uma matriz de objetos. O rascunho atual das shared_ptralterações previstas para este TS pode ser encontrado na N4082 . Essas alterações serão acessíveis através do std::experimentalnamespace e incluídas no <experimental/memory>cabeçalho. Algumas das alterações relevantes para dar suporte shared_ptra matrizes são:

- A definição do tipo de membro é element_typealterada

typedef T element_type;

 typedef typename remove_extent<T>::type element_type;

- Membro operator[]está sendo adicionado

 element_type& operator[](ptrdiff_t i) const noexcept;

- Diferentemente da unique_ptrespecialização parcial para matrizes, ambos shared_ptr<T[]>e shared_ptr<T[N]>serão válidos e ambos resultarão na delete[]chamada na matriz gerenciada de objetos.

 template<class Y> explicit shared_ptr(Y* p);

Requer : Ydeve ser do tipo completo. A expressão delete[] pquando Té um tipo de matriz ou delete p, quando Tnão é um tipo de matriz, deve ser bem formada, deve ter um comportamento bem definido e não deve gerar exceções. Quando Té U[N], Y(*)[N]deve ser conversível em T*; Quando Té U[], Y(*)[]deve ser conversível em T*; caso contrário, Y*será convertível em T*.

Pretoriano
fonte
9
+1, observação: também há Boost's shared-array.
jogojapan
5
@ tshah06 shared_ptr::getretorna um ponteiro para o objeto gerenciado. Para que você possa usá-lo comosp.get()[0] = 1; ... sp.get()[9] = 10;
Pretoriano
55
ALT: std::shared_ptr<int> sp( new int[10], std::default_delete<int[]>() );consulte também en.cppreference.com/w/cpp/memory/default_delete
yohjp
2
@ Jeremy Se o tamanho é conhecido em tempo de compilação, não há necessidade de escrever uma classe para isso, std::shared_ptr<std::array<int,N>>deve ser o suficiente.
Praetorian
13
Por que unique_ptrobtém essa especialização parcial, mas shared_ptrnão obtém ?
Adam
28

Uma alternativa possivelmente mais fácil que você possa usar é shared_ptr<vector<int>>.

Timmmm
fonte
5
Sim, ele é. Ou um vetor é um superconjunto de uma matriz - ele tem a mesma representação na memória (mais metadados), mas é redimensionável. Realmente não há situações em que você deseja uma matriz, mas não pode usar um vetor.
Timmmm
2
A diferença, aqui, é que o tamanho do vetor é mais estático e o acesso aos dados será feito com uma dupla indireção. Se o desempenho não for o problema crítico, isso funcionará; caso contrário, o compartilhamento de uma matriz poderá ter seu próprio motivo.
Emilio Garavaglia
4
Então você provavelmente pode usar shared_ptr<array<int, 6>>.
Timmmm
10
A outra diferença é que é um pouco maior e mais lenta que uma matriz bruta. Geralmente não é realmente um problema, mas não vamos fingir que 1 == 1.1.
Andrew
2
Há situações em que a fonte dos dados na matriz significa que é difícil ou desnecessário converter em um vetor; como ao obter um quadro de uma câmera. (Ou, esse é o meu entendimento, de qualquer maneira)
Narfanator