Pergunta

Acho que entendo "a idéia básica" da semântica de mover, mas agora, quando estou no estágio de implementar meu próprio mapa, parei e comecei a pensar sobre isso quando escrevia um caso de uso e ando para mover ctor do mapa. Corrija -me se estou errado, mas o que eu entendo como todo o negócio de mover semântica funciona é que eles supõem ajudar a evitar cópias desnecessárias? Certo? Agora, pegue um mapa, por exemplo, e apenas para o Purpouse deste exemplo, assuma que meu mapa é modelado como:

class Map
{
Link* impl_;//THIS IS A POINTER TO A LINK WHICH HAS A parent, left and right (of Link type)
Map(Map&& tmp);//move ctor
//unnecessary code ommited
};

E aqui está o obstáculo: quando estou tentando pensar em mover ctor para o meu mapa, não consigo ver uma maneira de evitar alocar um novo espaço para todos os links que precisam ser criados e, em seguida, seus ponteiros trocados com os de um mapa TMP objeto (passado como um arg para o meu movimento).
Então, eu tenho que alocar espaço de qualquer maneira, ou não?

Foi útil?

Solução

Tudo o que você precisa fazer é reatribuir o ponteiro do link, uma vez que todos os outros indicadores de link estão anexados a ele, eles agora farão parte do novo mapa.

Map(Map&& tmp) :impl_(tmp.impl_) { tmp.impl_ = nullptr; }

Isso não assume nenhum outro membro de dados.

Outras dicas

Além do aviso padrão não reinventar os contêineres existentes, não seria suficiente simplesmente atribuir o ponteiro do nó raiz sem fazer alocação?

Você precisa de cinco operações no total: o clássico "Big Three" (construtor de cópias, operador de atribuição de cópia, destruidor) e as duas novas operações de movimentação (Construtor de mover, operador de atribuição de mover):

// destructor
~Map();

// copy constructor
Map(const Map& that);

// move constructor
Map(Map&& that)
{
    impl_ = that.impl_;
    that.impl_ = 0;
}

// copy assignment operator
Map& operator=(const Map& that);

// move assignment operator
Map& operator=(Map&& that)
{
    using std::swap;
    swap(impl_, that.impl_);
    return *this;
}

A idéia básica por trás do operador de atribuição de movimento é que swap(map1, map2) tem o mesmo efeito colateral observável que map1 = map2 Se você não inspecionar map2 Novamente depois de executar a troca. Lembre -se de que um rvalue é um prvalue ou um xvalue. Por definição, um cliente não podes Inspecione um objeto designado por um Prvvalue duas vezes, porque avalia um Prvvalue sempre leva à criação de um novo objeto. A única maneira de observar esse truque é passar de um XValue como std::move(map_variable), mas então é óbvio este map_variable é potencialmente modificado.

Se você deseja uma tarefa segura de exceção, mesmo quando copiar, você pode combinar ambos o operador de atribuição de cópia (tomando const Map&) e o operador de atribuição de movimentação (tomando Map&&) para um operador de atribuição generalizada (levando Map). Então você só precisa de quatro operações no total:

// exception safe copy/move assignment operator
Map& operator=(Map that)
{
    using std::swap;
    swap(impl_, that.impl_);
    return *this;
}

Observe que esta variante do operador de atribuição leva seu argumento por valor. Se o argumento for um LValue, o construtor de cópias inicializa that, e se o argumento for um rvalue, o construtor de movimentos faz o trabalho. (Observe também que especializar std::swap É improvável que resulte em ganhos de desempenho adicionais se você já fornecer as operações de movimentação.)

Há um excelente passo a passo da semântica de movimento de @FredOverflow aqui.

É possível implementar a semântica de movimentos com o antigo C ++ (não 0x), mas deve ser feito explicitamente e é mais complicado.

class X
{
// set access specifiers as required
   struct data
   {
      // all the members go here, just plain easy-to-copy members
   } m;

   data move()
   {
      data copy(m);
      m.reset(); // sets everything back to null state
      return m;
   }

   explicit X( const data& d ) : m(d)
   {
   }
  // other members including constructors
};

X::data func() // creates and returns an X
{
  X x; // construct whatever with what you want in it
  return x.move();
}

int main()
{
   X x(func());
   // do stuff with x
}

E X pode ser tornado não copiável e não atribuível a mais, os dados podem ter itens criados na pilha e é o destruidor de X que é responsável pela limpeza. Quando os dados são redefinidos pela função move (), o X de separação não terá nada para limpar porque a propriedade foi transferida.

Geralmente, a estrutura de dados deve ser pública dentro de X, mas todos os seus membros devem ser privados com x um amigo. Portanto, os usuários, portanto, não acessam nada lá diretamente.

Observe que é provável que "vaze" se ligar para mover () em x sem conectá -lo a outro x, portanto, se você chamar apenas func () acima, vazaria. Você também deve tomar cuidado se o construtor de x arremessos de dados (seu destruidor não for chamado, para que nenhuma limpeza acontecerá automaticamente). Se a própria cópia de Data Of Data for, você estará ainda mais com problemas. Geralmente, nenhum deles deve acontecer, pois os dados contêm coisas de luz (ponteiros e números) e não objetos pesados.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top