Pergunta

Eu tenho um B classe com um conjunto de construtores e um operador de atribuição.

Aqui está:

class B
{
 public:
  B();
  B(const string& s);
  B(const B& b) { (*this) = b; }
  B& operator=(const B & b);

 private:
  virtual void foo();
  // and other private member variables and functions
};

Eu quero criar um D classe herdada que só vai substituir a função foo(), e nenhuma outra alteração é necessária.

Mas, eu quero D ter o mesmo conjunto de construtores, incluindo construtor de cópia e operador de atribuição como B:

D(const D& d) { (*this) = d; }
D& operator=(const D& d);

Eu tenho que reescrever todos eles em D, ou há uma maneira de construtores e operador de uso de B? Eu particularmente quero evitar reescrever o operador de atribuição, porque tem de aceder a todas as variáveis ??de membro privadas de B.

Foi útil?

Solução

Você pode chamar explicitamente construtores e operadores de atribuição:

class Base {
//...
public:
    Base(const Base&) { /*...*/ }
    Base& operator=(const Base&) { /*...*/ }
};

class Derived : public Base
{
    int additional_;
public:
    Derived(const Derived& d)
        : Base(d) // dispatch to base copy constructor
        , additional_(d.additional_)
    {
    }

    Derived& operator=(const Derived& d)
    {
        Base::operator=(d);
        additional_ = d.additional_;
        return *this;
    }
};

O interessante é que isso funciona mesmo se você não definir explicitamente essas funções (que, em seguida, usa as funções do compilador gerado).

class ImplicitBase { 
    int value_; 
    // No operator=() defined
};

class Derived : public ImplicitBase {
    const char* name_;
public:
    Derived& operator=(const Derived& d)
    {
         ImplicitBase::operator=(d); // Call compiler generated operator=
         name_ = strdup(d.name_);
         return *this;
    }
};  

Outras dicas

Resposta curta: Sim, você terá que repetir o trabalho em D

Resposta longa:

Se a sua classe derivada 'D' não contém novas variáveis ??de membro, em seguida, as versões padrão (gerados pelo compilador deve funcionar muito bem). O construtor de cópia padrão irá chamar o construtor de cópia pai e o operador de atribuição padrão irá chamar o operador de atribuição pai.

Mas se sua classe 'D' contém recursos, então você terá que fazer algum trabalho.

I encontrar o seu construtor de cópia um pouco estranho:

B(const B& b){(*this) = b;}

D(const D& d){(*this) = d;}

Normalmente construtores de cópia corrente de forma que eles são cópia construída a partir da base para cima. Aqui porque você está chamando o operador de atribuição do construtor de cópia deve chamar o construtor padrão para o padrão inicializar o objeto de baixo para cima em primeiro lugar. Então você vai para baixo novamente usando o operador de atribuição. Isso parece bastante ineficiente.

Agora, se você fizer uma atribuição que você está copiando a partir do até inferior (ou de cima para baixo), mas parece difícil para você fazer isso e fornecer uma garantia exceção forte. Se a qualquer momento um recurso falhar para copiar e você lançar uma exceção o objeto estará em um estado indeterminado (que é uma coisa ruim).

Normalmente, eu já vi isso feito o contrário.
O operador de atribuição é definido em termos do construtor de cópia e swap. Isso é porque ele torna mais fácil para fornecer a garantia forte exceção. Eu não acho que você será capaz de fornecer a garantia forte por fazê-lo desta maneira ao redor (posso estar errado).

class X
{
    // If your class has no resources then use the default version.
    // Dynamically allocated memory is a resource.
    // If any members have a constructor that throws then you will need to
    // write your owen version of these to make it exception safe.


    X(X const& copy)
      // Do most of the work here in the initializer list
    { /* Do some Work Here */}

    X& operator=(X const& copy)
    {
        X tmp(copy);      // All resource all allocation happens here.
                          // If this fails the copy will throw an exception 
                          // and 'this' object is unaffected by the exception.
        swap(tmp);
        return *this;
    }
    // swap is usually trivial to implement
    // and you should easily be able to provide the no-throw guarantee.
    void swap(X& s) throws()
    {
        /* Swap all members */
    }
};

Mesmo se você derivar uma classe D a partir de X isso não afeta este padrão.
É certo que você precisará repetir um pouco do trabalho por fazer chamadas explícitas para a classe base, mas isso é relativamente trivial.

class D: public X
{

    // Note:
    // If D contains no members and only a new version of foo()
    // Then the default version of these will work fine.

    D(D const& copy)
      :X(copy)  // Chain X's copy constructor
      // Do most of D's work here in the initializer list
    { /* More here */}



    D& operator=(D const& copy)
    {
        D tmp(copy);      // All resource all allocation happens here.
                          // If this fails the copy will throw an exception 
                          // and 'this' object is unaffected by the exception.
        swap(tmp);
        return *this;
    }
    // swap is usually trivial to implement
    // and you should easily be able to provide the no-throw guarantee.
    void swap(D& s) throws()
    {
        X::swap(s); // swap the base class members
        /* Swap all D members */
    }
};

Você provavelmente tem uma falha em seu projeto (sugestão: corte , semântica entidade vs semântica de valor ). Ter uma cópia integral / semântica de valor em um objeto a partir de uma hierarquia polimórfica muitas vezes não é uma necessidade em tudo. Se você deseja fornecer-lo apenas no caso de alguém pode precisar dele mais tarde, isso significa que você nunca vai precisar. Faça o não copiável classe base em vez (por herança de boost :: noncopyable por exemplo), e isso é tudo.

As soluções única corretos quando tal necessidade realmente aparecer são o envelope letras idioma , ou o pequeno quadro do artigo sobre objetos regulares por Sean Pai e Alexander Stepanov IIRC. Todas as outras soluções irão dar-lhe problemas com a corte, e / ou o LSP.

Sobre o assunto, ver também C ++ CoreReference C.67: C.67:. a classe base deve suprimir copiar, e fornecer um clone virtual em vez se "copiar" é desejada

Você terá que redefinir todos os construtores que não são padrão ou cópia construtores. Você não precisa redefinir o construtor de cópia nem operador de atribuição como os fornecidos pelo compilador (de acordo com o padrão) vai chamar todas as versões da base:

struct base
{
   base() { std::cout << "base()" << std::endl; }
   base( base const & ) { std::cout << "base(base const &)" << std::endl; }
   base& operator=( base const & ) { std::cout << "base::=" << std::endl; }
};
struct derived : public base
{
   // compiler will generate:
   // derived() : base() {}
   // derived( derived const & d ) : base( d ) {}
   // derived& operator=( derived const & rhs ) {
   //    base::operator=( rhs );
   //    return *this;
   // }
};
int main()
{
   derived d1;      // will printout base()
   derived d2 = d1; // will printout base(base const &)
   d2 = d1;         // will printout base::=
}

Note que, como SBI observou, se você definir qualquer construtor o compilador não irá gerar o construtor padrão para você e que inclui o construtor de cópia.

O código original está errado:

class B
{
public:
    B(const B& b){(*this) = b;} // copy constructor in function of the copy assignment
    B& operator= (const B& b); // copy assignment
 private:
// private member variables and functions
};

Em geral, você não pode definir o construtor de cópia em termos de atribuição de cópia, porque a atribuição de cópia deve liberar os recursos e o construtor de cópia não !!!

Para entender isso, considere:

class B
{
public:
    B(Other& ot) : ot_p(new Other(ot)) {}
    B(const B& b) {ot_p = new  Other(*b.ot_p);}
    B& operator= (const B& b);
private:
    Other* ot_p;
};

Para evitar vazamento de memória, a atribuição de cópia primeiro deve apagar a memória apontada por ot_p:

B::B& operator= (const B& b)
{
    delete(ot_p); // <-- This line is the difference between copy constructor and assignment.
    ot_p = new  Other(*b.ot_p);
}
void f(Other& ot, B& b)
{
    B b1(ot); // Here b1 is constructed requesting memory with  new
    b1 = b; // The internal memory used in b1.op_t MUST be deleted first !!!
}

Assim, construtor de cópia e cópia de atribuição são diferentes porque o primeiro construto e objeto em uma memória inicializado e, mais tarde, deve primeiro libertar a memória existente antes de construir o novo objeto.

Se você faz o que é originalmente sugerida neste artigo:

B(const B& b){(*this) = b;} // copy constructor

Você será apagar uma memória que não existe.

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