Pergunta

Eu estou aprendendo c++ e eu aprendi recentemente (aqui no estouro de pilha) sobre o copiar-e-swap de expressão e eu tenho algumas perguntas sobre o assunto.Assim, suponhamos que eu tenha a seguinte classe usando uma cópia-e-swap de expressão, apenas para exemplo:

class Foo {
private:
  int * foo;
  int size;

public:
  Foo(size_t size) : size(size) { foo = new int[size](); }
  ~Foo(){delete foo;}

  Foo(Foo const& other){
    size = other.size;
    foo = new int[size];
    copy(other.foo, other.foo + size, foo);
  }

  void swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
  }

  Foo& operator=(Foo g) { 
    g.swap(*this); 
    return *this; 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

A minha pergunta é, suponhamos que eu tenha uma outra classe que tenha um objeto Foo como dados, mas sem ponteiros ou outros recursos que podem precisar personalizado a cópia ou a atribuição de:

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) {}; 
  Bar& operator=(Bar other) {bar = other.bar;}
};

Agora eu tenho uma série de perguntas:

  1. São os métodos e construtores, como implementado acima para o Bar classe de segurança?Depois de ter usado o copiar-e-swap para Foo dão-me a certeza de que nenhum mal pode ser feito quando a atribuição ou cópia Bar?

  2. Passar o argumento por referência o construtor de cópia e de swap é obrigatória?

  3. É certo dizer que, quando o argumento de operator= é passado por valor, o construtor de cópia é chamado para esse argumento para gerar uma cópia temporária do objeto, e que é esta cópia que é então trocado com *this?Se eu passado por referência operator= Eu teria um grande problema, certo?

  4. Existem situações em que esta linguagem não fornece segurança completa em copiar e a atribuição de Foo?

Foi útil?

Solução

1 - São os métodos e construtores, como implementado acima para a classe de Barra de seguro?Depois de ter usado o copiar-e-swap para o Foo me certificar de que nenhum dano pode ser feito quando a atribuição ou a cópia de Bar?

Sobre a cópia do construtor:é sempre mais seguro (tudo ou nada).Ele conclui (todos), ou ele lança uma exceção (nada).

Se sua classe é composto de um membro (i.e.sem classes base), o operador de atribuição será tão seguro como o de membro de classe.Se você tiver mais de um membro, o operador de atribuição deixará de ser "tudo ou nada".O segundo membro do operador de atribuição pode lançar uma exceção, e, nesse caso, o objeto será atribuído "metade do caminho".Isso significa que você precisa para implementar copiar-e-trocar novamente na nova categoria de "tudo ou nada" atribuição.

No entanto, ele ainda vai ser "seguro", no sentido de que você não vazar quaisquer recursos.E, claro, o estado de cada membro, individualmente, vai ser consistente - apenas o estado de a nova classe de não ser consistente, porque um membro foi atribuído, e o outro não.

2 - Passar o argumento por referência o construtor de cópia e de swap é obrigatória?

Sim, a passagem por referência é obrigatória.O construtor de cópia é o que cópias de objetos, por isso não tome a sua argumento por valor, uma vez que significaria o argumento tem de ser copiado.Isto poderia levar a uma recursão infinita.(Copie o construtor seria chamado para o argumento, o que significa chamar a cópia do construtor para o argumento, o que significaria,...).Para trocar, o motivo é outro:se você passar o argumento por valor, você nunca poderia usar swap realmente troca o conteúdo de dois objetos - o "destino" de permuta seria uma cópia do objeto originalmente transmitida, que seria destruída imediatamente.

3 - É certo dizer que, quando o argumento do operador= é passado por valor, o construtor de cópia é chamado para esse argumento para gerar uma cópia temporária do objeto, e que é esta cópia que é, então, trocou com *esta?Se eu passado por referência no operador= eu teria um grande problema, certo?

Sim, é isso mesmo.No entanto, é também bastante comum para tirar o argumento por referência-para-const, construir uma cópia local e, em seguida, permuta com a cópia local.O referência-para-const forma tem algumas desvantagens, no entanto (desactiva algumas otimizações).Se você está não a implementação de copiar-e-swap, porém, você provavelmente deve passar por referência-para-const.

4 - existem situações em que esta linguagem não fornece segurança completa em copiar e atribuir Foo?

Nenhum que eu saiba.Claro que se pode sempre fazer as coisas falham com multi-threading (se não está sincronizada corretamente), mas isso deveria ser óbvio.

Outras dicas

Tanto quanto possível, você deve inicializar os membros de sua classe na lista de inicializador.Que também vai cuidar do bug que eu disse a você sobre o comentário.Com isso em mente, o código fica:

class Foo {
private:
  int size;
  int * foo;

public:
  Foo(size_t size) : size(size), foo(new int[size]) {}
  ~Foo(){delete[] foo;} // note operator delete[], not delete

  Foo(Foo const& other) : size(other.size), foo(new int[other.size]) {
    copy(other.foo, other.foo + size, foo);
  }

  Foo& swap(Foo& other) { 
    std::swap(foo,  other.foo);  
    std::swap(size, other.size); 
    return *this;
  }

  Foo& operator=(Foo g) { 
    return swap(g); 
  }

  int& operator[] (const int idx) {return foo[idx];}
};

e

class Bar {
private:
  Foo bar;
public:
  Bar(Foo foo) : bar(foo) {};
  ~Bar(){};
  Bar(Bar const& other) : bar(other.bar) { }
  Bar& swap(Bar &other) { bar.swap(other.bar); return *this; }
  Bar& operator=(Bar other) { return swap(other); }
}

que usa a mesma linguagem em todo

nota

como mencionado em um comentário sobre a questão, Bar's personalizados construtores de cópia, etc.são desnecessárias, mas vamos supor que Bar tem outras coisas também :-)

segunda questão

Passagem por referência swap é necessário, pois ambas as instâncias são alterados.

Passagem por referência para o construtor de cópia é necessária porque se passagem por valor, você precisa chamar o construtor de cópia

terceira questão

sim

quarta questão

não, mas não é sempre a maneira mais eficiente de fazer as coisas

Este é um clássico exemplo do que seguir um idioma leva para desnecessários penalidades de desempenho (prematuro pessimization).Isso não é culpa sua.O copiar-e-trocar a linguagem é a maneira mais badalada.Ele é uma boa expressão.Mas deve não ser seguido cegamente.

Nota: Um dos caras mais coisas que você pode fazer em um computador é alocar e desalocar memória.Vale a pena para evitar fazê-lo quando conveniente.

Nota: No seu exemplo, o copiar-e-swap de expressão de sempre executa um desalocação, e muitas vezes (quando a rhs para a atribuição é um lvalue) uma alocação de bem.

Observação: Quando size() == rhs.size(), não desatribuição ou alocação precisa ser feito.Tudo que você tem a fazer é uma copy.Este é muito, muito mais rápido.

Foo& operator=(const Foo& g) {
    if (size != g.size)
        Foo(g).swap(*this); 
    else
       copy(other.foo, other.foo + size, foo);
    return *this; 
}

I. e.seleção para o caso em que você pode reciclar recursos, em primeiro lugar.Em seguida, copie-e-swap (ou o que) se você não reciclar os seus recursos.

Nota: Meus comentários não contradizem as boas respostas dadas por outros, nem qualquer correção de problemas.O meu comentário é apenas sobre uma penalidade de desempenho significativos.

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