Sobrecarga do operador canônico?
-
05-07-2019 - |
Pergunta
Existe um padrão canônico ou recomendado para implementar a sobrecarga do operador aritmético em classes semelhantes a números C ++?
A partir das perguntas frequentes do C ++, temos um operador de tarefas à prova de exceção que evita a maioria dos problemas:
class NumberImpl;
class Number {
NumberImpl *Impl;
...
};
Number& Number::operator=(const Number &rhs)
{
NumberImpl* tmp = new NumberImpl(*rhs.Impl);
delete Impl;
Impl = tmp;
return *this;
}
Mas para outros operadores ( +, +=, etc ..) muito pouco conselho é dado além de fazê-los se comportar como os operadores em tipos internos.
Existe uma maneira padrão de defini -las? É isso que eu criei - existem armadilhas que não estou vendo?
// Member operator
Number& Number::operator+= (const Number &rhs)
{
Impl->Value += rhs.Impl->Value; // Obviously this is more complicated
return *this;
}
// Non-member non-friend addition operator
Number operator+(Number lhs, const Number &rhs)
{
return lhs += rhs;
}
Solução
No livro de Bjarne Stroustrup "A linguagem de programação C ++", no capítulo 11 (aquele dedicado ao sobrecarga do operador), ele passa por uma aula para um tipo de número complexo (Seção 11.3).
Uma coisa que percebo nessa seção é que ele implementa operações do tipo misto ... provavelmente é esperado para qualquer classe numérica.
Em geral, o que você tem parece bom.
Outras dicas
A grande coisa a considerar ao escrever qualquer operador é que os operadores de membros não sofrem conversões no parâmetro esquerdo:
struct example {
example(int);
example operator + (example);
};
void foo() {
example e(3), f(6);
e + 4; // okay: right operand is implicitly converted to example
e + f; // okay: no conversions needed.
6 + e; // BAD: no matching call.
}
Isso ocorre porque a conversão nunca se aplica a this
para funções de membro, e isso se estende aos operadores. Se o operador fosse em vez disso example operator + (example, example)
No espaço para nome global, ele compilaria (ou se fosse usado pass-por-consciar-ref).
Como resultado, operadores simétricos como +
e -
são geralmente implementados como não membros, enquanto os operadores de atribuição de compostos como +=
e -=
são implementados como membros (eles também mudam de dados, o que significa que devem ser membros). E, como você deseja evitar a duplicação de código, os operadores simétricos podem ser implementados em termos de atribuição de composto (como no seu exemplo de código, embora a Convenção recomende tornar o temporário dentro da função).
A convenção é escrever operator+(const T&)
e operator-(const T&)
em termos de operator+=(const T&)
e operator-=(const T&)
. Se fizer sentido adicionar e subtrair a/de tipos primitivos, você deve escrever um construtor que construa o objeto do tipo primitivo. Em seguida, os operadores sobrecarregados também funcionarão para tipos primitivos, porque o compilador chamará o construtor apropriado implicitamente.
Como você mencionou a si mesmo, você deve evitar dar privilégios de acesso a funções que não precisam. Mas em seu código acima para operator+(Number, const Number&)
Eu pessoalmente faria as duas referências const parâmetros e usaria uma temperatura. Eu acho que não surpreende que o comentarista abaixo da sua pergunta tenha perdido isso; A menos que você tenha um bom motivo para não, evite surpresas e truques e seja o mais óbvio possível.
Se você deseja que seu código se integre com outros tipos numéricos, digamos std::complex
, cuidado com as conversões cíclicas. Isto é, não forneça operator OtherNumeric()
dentro Numeric
E se OtherNumeric
fornece um construtor que leva um Numeric
parâmetro.
É tradicional escrever o operador x em termos do operador = x
Também é tradicional os parâmetros de todos os operadores padrão são const
// Member operator
// This was OK
Number& Number::operator+= (Number const& rhs)
{
Impl->Value += rhs.Impl->Value; // Obviously this is more complicated
return *this;
}
// Non-member non-friend addition operator
Number operator+(Number const& lhs,Number const& rhs)
{
// This I would set the lhs side to const.
// Make a copy into result.
// Then use += add the rhs
Number result(lhs);
return result += rhs;
}
Você menciona o operador de atribuição.
Mas você não mencionou o construtor de cópias. Como sua turma tem a propriedade de um ponteiro bruto, eu esperaria que você definisse isso também. O operador de atribuição é tradicionalmente escrito em termos do construtor de cópias.