Fornecendo semântica de movimento correta
-
14-11-2019 - |
Pergunta
Atualmente, estou tentando descobrir como mover a semântica corretamente com um objeto que contém um ponteiro para a memória alocada.Eu tenho uma grande estrutura de dados, que contém um ponteiro bruto interno para o armazenamento real (por razões de eficiência).Agora adicionei um construtor de movimento e move operator=()
.Nestes métodos eu sou std::move()
apontando o ponteiro para a nova estrutura.No entanto, não tenho certeza do que fazer com o ponteiro da outra estrutura.
Aqui está um exemplo simples do que estou fazendo:
class big_and_complicated {
// lots of complicated code
};
class structure {
public:
structure() :
m_data( new big_and_complicated() )
{}
structure( structure && rhs ) :
m_data( std::move( rhs.m_data ) )
{
// Maybe do something to rhs here?
}
~structure()
{
delete m_data;
}
private:
big_and_complicated * m_data;
}
int main() {
structure s1;
structure s2( std::move( s1 ) );
return 0;
}
Agora, pelo que entendi, depois do std::move( s1 )
para s2
a única coisa que é segura para fazer s1
ist para chamar seu construtor.No entanto, até onde posso ver, isso levaria à exclusão do ponteiro contido em s1
no destruidor, renderizando s2
inútil também.Então, acho que preciso fazer algo para tornar o destruidor seguro quando std::move()
o ponteiro.Tanto quanto posso ver, a coisa mais segura a fazer aqui é configurá-lo para 0
no objeto movido, pois isso transformaria o delete
em um ambiente autônomo mais tarde.Este raciocínio está correto até agora?Ou é std::move()
realmente inteligente o suficiente para anular o ponteiro para mim, tornando seu uso seguro?Até agora não estou vendo nenhuma falha no conjunto de testes real, mas não tive certeza de que o construtor de movimento foi realmente chamado.
Solução
"Mover" um ponteiro não é diferente de copiar um e não não defina o valor movido como nulo ('moving' está entre aspas aqui porque std::move
não move nada, apenas muda o categoria de valor do argumento).Basta copiar rhs
'ponteiro e então configure-o para nullptr
:
struct structure
{
structure()
: m_data{new big_and_complicated{}}
{ }
structure(structure&& rhs)
: m_data{rhs.m_data}
{
rhs.m_data = nullptr;
}
structure& operator =(structure&& rhs)
{
if (this != &rhs)
{
delete m_data;
m_data = rhs.m_data;
rhs.m_data = nullptr;
}
return *this;
}
~structure()
{
delete m_data;
}
private:
big_and_complicated* m_data;
structure(structure const&) = delete; // for exposition only
structure& operator =(structure const&) = delete; // for exposition only
}
Melhor ainda, use std::unique_ptr<big_and_complicated>
em vez de big_and_complicated*
e você não precisa definir nada disso sozinho:
#include <memory>
struct structure
{
structure()
: m_data{new big_and_complicated{}}
{ }
private:
std::unique_ptr<big_and_complicated> m_data;
}
Por último, a menos que você realmente queira structure
para permanecer não copiável, é melhor apenas implementar a semântica de movimento adequada dentro de big_and_complicated
e tendo structure
segure um big_and_complicated
objeto diretamente.