Nota: As respostas foram dadas em uma ordem específica, mas como muitos usuários ordenam as respostas de acordo com os votos, ao invés do tempo em que foram dadas, aqui's um índice das respostas na ordem em que elas fazem mais sentido:
(Nota: Isto pretende ser uma entrada para Stack Overflow's C++ FAQ. Se você quiser criticar a idéia de fornecer um FAQ neste formulário, então o post em meta que começou tudo isso seria o lugar para fazer isso. As respostas a essa pergunta são monitoradas no C++ chatroom, onde a idéia do FAQ começou em primeiro lugar, então é muito provável que sua resposta seja lida por aqueles que tiveram a idéia.)
A maior parte do trabalho nos operadores de sobrecarga é código de caldeira-placa. Isso é pouco surpreendente, já que os operadores são apenas açúcar sintáctico, seu trabalho real poderia ser feito por (e muitas vezes é encaminhado para) funções simples. Mas é importante que este código da chapa da caldeira esteja correto. Se você falhar, ou o código do seu operador não irá compilar ou o código dos seus usuários não irá compilar ou o código dos seus usuários irá se comportar de forma surpreendente.
Há muito a dizer sobre a missão. No entanto, a maioria já foi dita em GMan's famoso Copy-And-Swap FAQ, então I'irá pular a maior parte dela aqui, listando apenas o operador perfeito para referência:
X& X::operator=(X rhs)
{
swap(rhs);
return *this;
}
Os operadores bitshift <<
e >>
, embora ainda utilizados em interface de hardware para as funções de manipulação de bits que herdam de C, tornaram-se mais prevalentes como operadores de entrada e saída de fluxo sobrecarregados na maioria das aplicações. Para orientação sobre a sobrecarga como operadores de bit-manipulação, veja a seção abaixo sobre Operadores Aritméticos Binários. Para implementar seu próprio formato personalizado e lógica de análise quando seu objeto é usado com iostreams, continue.
Os operadores de stream, entre os operadores mais comumente sobrecarregados, são operadores infix binários para os quais a sintaxe não especifica nenhuma restrição sobre se eles devem ser membros ou não membros.
Como eles mudam seu argumento esquerdo (alteram o estado do fluxo), eles devem, de acordo com as regras básicas, ser implementados como membros de seu tipo de operando esquerdo. Entretanto, seus operandos esquerdos são fluxos da biblioteca padrão, e enquanto a maioria dos operadores de saída e entrada dos fluxos definidos pela biblioteca padrão são de fato definidos como membros das classes de fluxo, quando você implementa operações de saída e entrada para seus próprios tipos, você não pode alterar os tipos de fluxo da biblioteca padrão. É por isso que você precisa implementar esses operadores para os seus próprios tipos como funções não-membros.
As formas canônicas dos dois são estas:
std::ostream& operator<<(std::ostream& os, const T& obj)
{
// write obj to stream
return os;
}
std::istream& operator>>(std::istream& is, T& obj)
{
// read obj from stream
if( /* no valid object of T found in stream */ )
is.setstate(std::ios::failbit);
return is;
}
Ao implementar operator>>
, a configuração manual do estado do fluxo só é necessária quando a leitura em si é bem sucedida, mas o resultado não é o que seria esperado.
O operador de chamada de função, utilizado para criar objetos de função, também conhecidos como funtores, deve ser definido como uma função member, por isso tem sempre implícito o este
argumento de funções de membro. Além disso, ele pode ser sobrecarregado para tomar qualquer número de argumentos adicionais, incluindo zero.
Aqui's um exemplo da sintaxe:
class foo {
public:
// Overloaded call operator
int operator()(const std::string& y) {
// ...
}
};
Uso:
foo f;
int a = f("hello");
Em toda a biblioteca padrão C++, os objetos de função são sempre copiados. Seus próprios objetos de função devem, portanto, ser baratos de copiar. Se um objeto de função precisa absolutamente usar dados que são caros para copiar, é melhor armazenar esses dados em outro lugar e fazer com que o objeto de função se refira a ele.
Os operadores de comparação binária infix devem, de acordo com as regras de comparação, ser implementados como funções não-membros1. A negação do prefixo unário !
deve (de acordo com as mesmas regras) ser implementada como uma função de membro. (mas normalmente não é uma boa idéia sobrecarregá-la).
Os algoritmos da biblioteca padrão (por exemplo std::sort()
) e tipos (por exemplo std::map
) sempre esperam apenas que o operator<
esteja presente. Entretanto, os usuários do seu tipo também esperarão que todos os outros operadores estejam presentes, então se você definir operator<
, certifique-se de seguir a terceira regra fundamental de sobrecarga de operadores e também definir todos os outros operadores de comparação booleana. A forma canônica de implementá-los é esta:
inline bool operator==(const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator!=(const X& lhs, const X& rhs){return !operator==(lhs,rhs);}
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return operator< (rhs,lhs);}
inline bool operator<=(const X& lhs, const X& rhs){return !operator> (lhs,rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !operator< (lhs,rhs);}
O importante aqui é notar que apenas dois destes operadores fazem realmente qualquer coisa, os outros estão apenas encaminhando seus argumentos para qualquer um destes dois para fazer o trabalho real.
A sintaxe para sobrecarregar os operadores booleanos binários restantes (||
, &&
) segue as regras dos operadores de comparação. No entanto, é muito improvável que você encontre um caso de uso razoável para estes2.
1 Como acontece com todas as regras, às vezes pode haver razões para quebrar esta também. Se assim for, não se esqueça que o operando à esquerda dos operadores de comparação binária, que para funções de membro serão *this', precisa ser
const', também. Então um operador de comparação implementado como uma função de membro teria que ter esta assinatura:
bool operator<(const X& rhs) const { /* do actual comparison with *this */ }
(Note o const
no final.)
2 Deve-se notar que a versão embutida de ||
e &&
utiliza a semântica de atalho. Enquanto os definidos pelo usuário (porque são sintáticos para chamadas de método) não utilizam semântica de atalho. O usuário vai esperar que esses operadores tenham semântica de atalho, e seu código pode depender disso, portanto é altamente recomendado NUNCA defini-los.
Os operadores de incremento e decremento unário vêm tanto no sabor prefixo como no sabor pós-fixo. Para distinguir um do outro, as variantes postfix tomam um argumento adicional dummy int. Se você sobrecarregar o incremento ou decremento, certifique-se sempre de implementar ambas as versões de prefixo e postfix. Aqui está a implementação canônica do incremento, o decremento segue as mesmas regras:
class X {
X& operator++()
{
// do actual increment
return *this;
}
X operator++(int)
{
X tmp(*this);
operator++();
return tmp;
}
};
Observe que a variante postfix é implementada em termos de prefixo. Note também que o postfix faz uma cópia extra.2
A sobrecarga do unary menos e mais não é muito comum e provavelmente é melhor evitar. Se necessário, eles provavelmente devem ser sobrecarregados como funções de membro.
2 Note também que a variante postfix faz mais trabalho e, portanto, é menos eficiente de usar do que a variante prefixada. Esta é uma boa razão para geralmente preferir o incremento de prefixo em vez do incremento de prefixo. Enquanto os compiladores podem normalmente otimizar o trabalho adicional do incremento de prefixo para tipos incorporados, eles podem não ser capazes de fazer o mesmo para tipos definidos pelo usuário (que pode ser algo tão inocentemente parecido como um iterador de lista). Uma vez acostumado a fazer i++
, torna-se muito difícil lembrar de fazer ++i
quando o i
não é do tipo built-in (mais você'teria que mudar o código ao mudar um tipo), então é melhor criar o hábito de sempre utilizar o incremento de prefixo, a menos que o postfix seja explicitamente necessário.
Para os operadores aritméticos binários, não se esqueça de obedecer à terceira regra básica de sobrecarga do operador: Se você fornecer +
, forneça também +=
, se você fornecer -
, não omitir -=
, etc. Diz-se que Andrew Koenig foi o primeiro a observar que os operadores de atribuição composta podem ser utilizados como base para seus contrapartes não-compostos. Ou seja, o operador +
é implementado em termos de +=
, -
é implementado em termos de -=
, etc.
De acordo com nossas regras, +
e seus companheiros devem ser não-membros, enquanto seus contrapartes de atribuição composta (+=`` etc.), mudando seu argumento esquerdo, deve ser um membro. Aqui está o código exemplar para
+=`e
+`; os outros operadores aritméticos binários devem ser implementados da mesma forma:
class X {
X& operator+=(const X& rhs)
{
// actual addition of rhs to *this
return *this;
}
};
inline X operator+(X lhs, const X& rhs)
{
lhs += rhs;
return lhs;
}
operator+=retorna o seu resultado por referência, enquanto o
operator+retorna uma cópia do seu resultado. Claro, retornar uma referência é normalmente mais eficiente do que retornar uma cópia, mas no caso do
operator+, não há como contornar a cópia. Quando você escreve
a + b, você espera que o resultado seja um novo valor, e é por isso que o
operator+tem que retornar um novo valor.<sup>3</sup> Note também que o
operator+pega seu operando esquerdo ___ por cópia___ em vez de por referência constante. A razão para isto é a mesma que a razão dada para o
operator=pegar seu argumento por cópia. Os operadores de manipulação de bits
~`&
`|
^
<<
>>
devem ser implementados da mesma forma que os operadores aritméticos. Entretanto, (exceto para sobrecarga <<
e >>
para saída e entrada) há muito poucos casos razoáveis de uso para sobrecarga destes.
3 Novamente, a lição a ser tirada é que a += b
é, em geral, mais eficiente do que a + b
e deve ser preferida se possível.
O operador de array subscript é um operador binário que deve ser implementado como um membro da classe. É utilizado para tipos de contentores que permitem o acesso aos seus elementos de dados através de uma chave. A forma canônica de fornecê-los é esta:
class X {
value_type& operator[](index_type idx);
const value_type& operator[](index_type idx) const;
// ...
};
A menos que você não queira que os usuários da sua classe possam alterar os elementos de dados devolvidos pelo operador[]
(nesse caso você pode omitir a variante não-constante), você deve sempre fornecer ambas as variantes do operador.
Se o value_type é conhecido por se referir a um tipo incorporado, a variante const do operador deve retornar melhor uma cópia do que uma referência const:
class X {
value_type& operator[](index_type idx);
value_type operator[](index_type idx) const;
// ...
};
Para definir seus próprios iteradores ou apontadores inteligentes, você tem que sobrecarregar o operador de desreferenciamento de prefixo unário *' e o operador de acesso de membro com ponteiro infixo binário
->`:
class my_ptr {
value_type& operator*();
const value_type& operator*() const;
value_type* operator->();
const value_type* operator->() const;
};
->
, se value_type
for do tipo class
(ou struct
ou union
), outro operator->()
é chamado recursivamente, até que um operator->()
retorne um valor do tipo não-classe.
O endereço unário do operador nunca deve ser sobrecarregado.
Para operator->*()
veja esta pergunta. It's raramente utilizado e, portanto, raramente sobrecarregado. Na verdade, mesmo os iteradores não o sobrecarregam.Continuar para Operadores de Conversão
Quando se trata de sobrecarga do operador em C++, existem três regras básicas que você deve seguir. Como com todas essas regras, existem de fato exceções. Algumas vezes as pessoas se desviaram delas e o resultado não foi um código ruim, mas tais desvios positivos são poucos e distantes entre eles. No mínimo, 99 de cada 100 desvios que vi foram injustificados. No entanto, poderia muito bem ter sido 999 de um total de 1000. Portanto, é melhor seguir as seguintes regras.
Quando o significado de um operador não é obviamente claro e indiscutível, ele não deve ser sobrecarregado. __ Em vez disso, forneça uma função com um nome bem escolhido. Basicamente, a primeira e principal regra para sobrecarregar os operadores, em seu próprio coração, diz: Não o faça. Isso pode parecer estranho, porque há muito a ser conhecido sobre sobrecarga de operadores e por isso muitos artigos, capítulos de livros e outros textos tratam de tudo isso. Mas apesar desta evidência aparentemente óbvia, são apenas surpreendentemente poucos os casos em que a sobrecarga do operador é apropriada. A razão é que na verdade é difícil entender a semântica por trás da aplicação de um operador, a menos que o uso do operador no domínio da aplicação seja bem conhecido e indiscutível. Ao contrário da crença popular, este quase nunca é o caso.
+
para subtrair de seu operando correto. Entretanto, os usuários de tal operador nunca suspeitariam da expressão a + b' para subtrair
a' de `b'. Claro, isto supõe que a semântica do operador no domínio da aplicação é indiscutível.a + b
, os usuários esperam poder chamar a += b
, também. Se suportar o incremento de prefixo ++a
, eles esperarão que a++
funcione também. Se eles podem verificar se a < b
, eles certamente esperarão também ser capazes de verificar se a > b
. Se eles podem copiar-construir o seu tipo, eles esperam que a tarefa também funcione.Continue para A Decisão entre Sócio e Não Sócio.
Não é possível alterar o significado dos operadores para tipos incorporados em C++, os operadores só podem ser sobrecarregados para tipos definidos pelo usuário1. Ou seja, pelo menos um dos operandos tem que ser de um tipo definido pelo usuário. Assim como em outras funções sobrecarregadas, os operadores podem ser sobrecarregados para um determinado conjunto de parâmetros apenas uma vez.
Nem todos os operadores podem ser sobrecarregados em C++. Entre os operadores que não podem ser sobrecarregados estão: .
::
tamanho de`typeid
.*
e o único operador ternário em C++, ?:
Entre os operadores que podem ser sobrecarregados em C++ estão estes:
+
-
``*
/
%
e +=
-=
*=
/=
%=
(todos infixos binários); +
-
(prefixo unário); ++
--
(prefixo unário e postfixos)&
`` ``` ``<<
>>`` e
&=`|= ``
^=`<<=
>>=
(todos os infixos binários); ~
(prefixo unário)==
!=
<
>
>=
>=
`||
&&` `` (tudo infixo binário);
!`
(prefixo unário)new
new[]
delete`delete[]
delete[]`=
[]
->`
->`,
`(todos os infix binários);
`&`` (todos os prefixos unários)
()` ` (chamada de função, n-ary infix)No entanto, o fato de você can sobrecarregar todas elas não significa que você deve fazer isso. Veja as regras básicas de sobrecarga do operador.
Em C++, os operadores estão sobrecarregados na forma de funções com nomes especiais. Como em outras funções, operadores sobrecarregados podem geralmente ser implementados como uma função member de seu operando esquerdo's tipo ou como funções non-member. Se você é livre para escolher ou obrigado a utilizar qualquer uma delas depende de vários critérios.2 Um operador unário @
3, aplicado a um objeto x, é invocado como operador@(x)
ou como x.operator@()
. Um operador binário infix @
, aplicado aos objetos x
e y
, é chamado como operator@(x,y)
ou como x.operator@(y)
.4
Os operadores que são implementados como funções de não-membros são por vezes amigos do seu tipo de operando.
1 O termo "definido pelo utilizador" pode ser ligeiramente enganador. C++ faz a distinção entre tipos incorporados e tipos definidos pelo usuário. Aos primeiros pertencem, por exemplo, int, char e double; aos segundos pertencem todos os tipos de estrutura, classe, união e enumeração, incluindo os da biblioteca padrão, mesmo que não sejam, como tal, definidos pelos usuários.
2 Isto é abordado em uma parte posterior desta FAQ.
3 O @
não é um operador válido em C++ e é por isso que o utilizo como um placeholder.
4 O único operador ternário em C++ não pode ser sobrecarregado e o único operador n-ary deve ser sempre implementado como uma função de membro.
Continue para The Three Basic Rules of Operator Overloading in C++.