Como usar construtores da classe base e operador de atribuição em C ++?
-
22-07-2019 - |
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
.
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.