operador de atribuição virtual C ++

Operador de atribuição em C ++ pode ser feito virtual. Por que isso é necessário? Podemos tornar outros operadores virtuais também?

O operador de atribuição não precisa ser virtual.

A discussão abaixo é sobre operator= , mas também se aplica a qualquer sobrecarga de operador que leva no tipo em questão e qualquer function que leva no tipo em questão.

A discussão abaixo mostra que a palavra-chave virtual não sabe sobre a inheritance de um parâmetro em relação à localização de uma assinatura de function correspondente. No exemplo final, mostra como manipular apropriadamente a atribuição ao lidar com tipos herdados.


As funções virtuais não sabem sobre a inheritance do parâmetro:

A assinatura de uma function precisa ser a mesma para o virtual entrar em jogo. Portanto, embora no exemplo a seguir, operator = seja virtual, a chamada nunca funcionará como uma function virtual em D, porque os parâmetros e o valor de retorno de operator = são diferentes.

A function B::operator=(const B& right) e D::operator=(const D& right) são 100% completamente diferentes e são vistas como 2 funções distintas.

 class B { public: virtual B& operator=(const B& right) { x = right.x; return *this; } int x; }; class D : public B { public: virtual D& operator=(const D& right) { x = right.x; y = right.y; return *this; } int y; }; 

Valores padrão e com 2 operadores sobrecarregados:

Você pode, entretanto, definir uma function virtual para permitir que você defina valores padrão para D quando for atribuído a uma variável do tipo B. Isto é mesmo se sua variável B for realmente um D armazenado em uma referência de um B. Você não obterá a Função D::operator=(const D& right) .

No caso abaixo, uma atribuição de 2 objects D armazenados dentro de referências 2 B … a substituição D::operator=(const B& right) é usada.

 //Use same B as above class D : public B { public: virtual D& operator=(const D& right) { x = right.x; y = right.y; return *this; } virtual B& operator=(const B& right) { x = right.x; y = 13;//Default value return *this; } int y; }; int main(int argc, char **argv) { D d1; B &b1 = d1; d1.x = 99; d1.y = 100; printf("d1.x d1.y %i %i\n", d1.x, d1.y); D d2; B &b2 = d2; b2 = b1; printf("d2.x d2.y %i %i\n", d2.x, d2.y); return 0; } 

Impressões:

 d1.x d1.y 99 100 d2.x d2.y 99 13 

O que mostra que D::operator=(const D& right) nunca é usado.

Sem a palavra-chave virtual em B::operator=(const B& right) você teria os mesmos resultados acima, mas o valor de y não seria inicializado. Ou seja, ele usaria o B::operator=(const B& right)


Um último passo para juntar tudo, RTTI:

Você pode usar o RTTI para manipular adequadamente as funções virtuais do seu tipo. Aqui está a última peça do quebra-cabeça para descobrir como lidar adequadamente com a atribuição ao lidar com possíveis tipos herdados.

 virtual B& operator=(const B& right) { const D *pD = dynamic_cast(&right); if(pD) { x = pD->x; y = pD->y; } else { x = right.x; y = 13;//default value } return *this; } 

Depende do operador.

O objective de tornar um operador de atribuição virtual é permitir que você seja capaz de substituí-lo para copiar mais campos.

Portanto, se você tiver uma Base & e tiver realmente um tipo Derivado & dynamic, e o Derivado tiver mais campos, as coisas corretas serão copiadas.

No entanto, há um risco de que seu LHS seja um Derivado, e o RHS é uma Base, portanto, quando o operador virtual é executado em Derivado, seu parâmetro não é um Derivado e você não tem como obter campos dele.

Aqui está uma boa discussão: http://icu-project.org/docs/papers/cpp_report/the_assignment_operator_revisited.html

Brian R. Bondy escreveu:


Um último passo para juntar tudo, RTTI:

Você pode usar o RTTI para manipular adequadamente as funções virtuais do seu tipo. Aqui está a última peça do quebra-cabeça para descobrir como lidar adequadamente com a atribuição ao lidar com possíveis tipos herdados.

 virtual B& operator=(const B& right) { const D *pD = dynamic_cast(&right); if(pD) { x = pD->x; y = pD->y; } else { x = right.x; y = 13;//default value } return *this; } 

Gostaria de acrescentar a esta solução algumas observações. Ter o operador de atribuição declarado o mesmo acima tem três problemas.

O compilador gera um operador de atribuição que recebe um const D & Argumento que não é virtual e não faz o que você acha que ele faz.

O segundo problema é o tipo de retorno, você está retornando uma referência base para uma instância derivada. Provavelmente não é um problema, já que o código funciona de qualquer maneira. Ainda é melhor devolver as referências em conformidade.

Terceira questão, o operador de atribuição de tipo derivado não chama operador de atribuição de class base (e se houver campos privados que você gostaria de copiar?), Declarar o operador de atribuição como virtual não fará com que o compilador gere um para você. Esse é um efeito colateral de não ter pelo menos duas sobrecargas do operador de atribuição para obter o resultado desejado.

Considerando a class base (igual à do post que citei):

 class B { public: virtual B& operator=(const B& right) { x = right.x; return *this; } int x; }; 

O código a seguir completa a solução RTTI citada:

 class D : public B{ public: // The virtual keyword is optional here because this // method has already been declared virtual in B class /* virtual */ const D& operator =(const B& b){ // Copy fields for base class B::operator =(b); try{ const D& d = dynamic_cast(b); // Copy D fields y = dy; } catch (std::bad_cast){ // Set default values or do nothing } return *this; } // Overload the assignment operator // It is required to have the virtual keyword because // you are defining a new method. Even if other methods // with the same name are declared virtual it doesn't // make this one virtual. virtual const D& operator =(const D& d){ // Copy fields from B B::operator =(d); // Copy D fields y = dy; return *this; } int y; }; 

Isso pode parecer uma solução completa, não é. Esta não é uma solução completa porque quando você deriva de D você precisará de 1 operator = que assume const B & , 1 operator = que toma const D & e um operador que toma const D2 & . A conclusão é óbvia, o número de sobrecargas operator = () é equivalente com o número de super classs + 1.

Considerando que D2 herda D, vamos dar uma olhada em como os dois methods operator = () herdados se parecem.

 class D2 : public D{ /* virtual */ const D2& operator =(const B& b){ D::operator =(b); // Maybe it's a D instance referenced by a B reference. try{ const D2& d2 = dynamic_cast(b); // Copy D2 stuff } catch (std::bad_cast){ // Set defaults or do nothing } return *this; } /* virtual */ const D2& operator =(const D& d){ D::operator =(d); try{ const D2& d2 = dynamic_cast(d); // Copy D2 stuff } catch (std::bad_cast){ // Set defaults or do nothing } return *this; } }; 

É óbvio que o operador = (const D2 &) apenas copia campos, imagine como se estivesse lá. Podemos notar um padrão nas sobrecargas operator = () herdadas. Infelizmente, não podemos definir methods de modelo virtual que cuidarão desse padrão, precisamos copiar e colar várias vezes o mesmo código para obter um operador de atribuição polimórfico completo, a única solução que vejo. Também se aplica a outros operadores binários.


Editar

Como mencionado nos comentários, o mínimo que pode ser feito para tornar a vida mais fácil é definir o operador de atribuição de superclass mais alto = () e chamá-lo de todos os outros methods de operador de superclass = (). Além disso, ao copiar campos, um método _copy pode ser definido.

 class B{ public: // _copy() not required for base class virtual const B& operator =(const B& b){ x = bx; return *this; } int x; }; // Copy method usage class D1 : public B{ private: void _copy(const D1& d1){ y = d1.y; } public: /* virtual */ const D1& operator =(const B& b){ B::operator =(b); try{ _copy(dynamic_cast(b)); } catch (std::bad_cast){ // Set defaults or do nothing. } return *this; } virtual const D1& operator =(const D1& d1){ B::operator =(d1); _copy(d1); return *this; } int y; }; class D2 : public D1{ private: void _copy(const D2& d2){ z = d2.z; } public: // Top-most superclass operator = definition /* virtual */ const D2& operator =(const B& b){ D1::operator =(b); try{ _copy(dynamic_cast(b)); } catch (std::bad_cast){ // Set defaults or do nothing } return *this; } // Same body for other superclass arguments /* virtual */ const D2& operator =(const D1& d1){ // Conversion to superclass reference // should not throw exception. // Call base operator() overload. return D2::operator =(dynamic_cast(d1)); } // The current class operator =() virtual const D2& operator =(const D2& d2){ D1::operator =(d2); _copy(d2); return *this; } int z; }; 

Não há necessidade de um método set defaults porque ele receberia apenas uma chamada (na sobrecarga do operador base = ()). Altera quando os campos de cópia são feitos em um local e todas as sobrecargas operator = () são afetadas e levam sua finalidade pretendida.

Obrigado sehe pela sugestão.

A atribuição virtual é usada nos cenários abaixo:

 //code snippet Class Base; Class Child :public Base; Child obj1 , obj2; Base *ptr1 , *ptr2; ptr1= &obj1; ptr2= &obj2 ; //Virtual Function prototypes: Base& operator=(const Base& obj); Child& operator=(const Child& obj); 

caso 1: obj1 = obj2;

Neste conceito virtual não desempenha nenhum papel, como chamamos operator= na class Child .

Caso 2 e 3: * ptr1 = obj2;
* ptr1 = * ptr2;

Aqui a atribuição não será como esperada. Razão sendo operator= é chamado na class Base .

Pode ser corrigido usando:
1) Fundição

 dynamic_cast(*ptr1) = obj2; // *(dynamic_cast(ptr1))=obj2;` dynamic_cast(*ptr1) = dynamic_cast(*ptr2)` 

2) conceito virtual

Agora, simplesmente usando virtual Base& operator=(const Base& obj) não ajudará, pois as assinaturas são diferentes em Child e Base para operator= .

Precisamos adicionar Base& operator=(const Base& obj) na class Child junto com sua definição usual Child& operator=(const Child& obj) . É importante include a definição posterior, como na ausência desse operador de atribuição padrão será chamado. ( obj1=obj2 pode não dar resultado desejado)

 Base& operator=(const Base& obj) { return operator=(dynamic_cast(const_cast(obj))); } 

caso 4: obj1 = * ptr2;

Nesse caso, o compilador procura a definição operator=(Base& obj) em Child como operator= é chamado em Child. Mas como o tipo não presente e o tipo Base não podem ser promovidos para o child implicitamente, será por erro (a conversão é necessária como obj1=dynamic_cast(*ptr1); )

Se implementarmos de acordo com o caso 2 e 3, esse cenário será resolvido.

Como pode ser visto, a atribuição virtual torna a chamada mais elegante no caso de atribuições usando referências / referências de class Base.

Podemos tornar outros operadores virtuais também? sim

É necessário apenas quando você quer garantir que as classs derivadas de sua class obtenham todos os seus membros copiados corretamente. Se você não está fazendo nada com polymorphism, então não precisa se preocupar com isso.

Eu não sei de nada que possa impedi-lo de virtualizar qualquer operador que você quer – eles são nada além de chamadas de método de caso especial.

Esta página fornece uma descrição excelente e detalhada de como tudo isso funciona.

Um operador é um método com uma syntax especial. Você pode tratá-lo como qualquer outro método …