Sou um grande fã de deixar o compilador trabalhar o máximo possível para você. Ao escrever uma classe simples, o compilador pode fornecer o seguinte de graça:
- Um construtor padrão (vazio)
- Um construtor de cópias
- Um destruidor
- Um operador de atribuição (
operator=
)
Mas isso não parece fornecer operadores de comparação - como operator==
ou operator!=
. Por exemplo:
class foo
{
public:
std::string str_;
int n_;
};
foo f1; // Works
foo f2(f1); // Works
foo f3;
f3 = f2; // Works
if (f3 == f2) // Fails
{ }
if (f3 != f2) // Fails
{ }
Existe uma boa razão para isso? Por que realizar uma comparação membro a membro seria um problema? Obviamente, se a classe alocar memória, você gostaria de ter cuidado, mas para uma classe simples, certamente o compilador poderia fazer isso por você?
==
, da mesma forma que existe uma atribuição automática padrão (=
) sob certas condições. (O argumento sobre ponteiros é inconsistente porque a lógica se aplica a=
e==
, e não apenas ao segundo).Respostas:
O compilador não saberia se você queria uma comparação de ponteiro ou uma comparação profunda (interna).
É mais seguro simplesmente não implementá-lo e deixar que o programador faça isso sozinho. Então eles podem fazer todas as suposições que quiserem.
fonte
operator=
) geralmente trabalham no mesmo contexto que os operadores de comparação - ou seja, há uma expectativa de que, após a execuçãoa = b
,a == b
seja verdadeira. Definitivamente, faz sentido que o compilador forneça um padrãooperator==
usando a mesma semântica de valor agregado usada para eleoperator=
. Eu suspeito que paercebal esteja realmente correto aqui, poisoperator=
(e copie o ctor) são fornecidos apenas para compatibilidade com C, e eles não queriam piorar a situação.O argumento de que, se o compilador puder fornecer um construtor de cópia padrão, ele deve ser capaz de fornecer um padrão semelhante
operator==()
faz certo sentido. Eu acho que o motivo da decisão de não fornecer um padrão gerado pelo compilador para esse operador pode ser adivinhado pelo que Stroustrup disse sobre o construtor de cópias padrão em "O Design e a Evolução do C ++" (Seção 11.4.1 - Controle de cópia) :Então, em vez de "por que o C ++ não possui um padrão
operator==()
?", A pergunta deveria ter sido "por que o C ++ tem uma atribuição padrão e um construtor de cópias?", Com a resposta sendo que esses itens foram incluídos com relutância pelo Stroustrup para compatibilidade com versões anteriores do C (provavelmente a causa da maioria das verrugas de C ++, mas também provavelmente o principal motivo da popularidade de C ++).Para meus próprios propósitos, no meu IDE, o snippet que eu uso para novas classes contém declarações para um operador de atribuição privada e construtor de cópias, de modo que, ao gerar uma nova classe, não obtenho operações de atribuição e cópia padrão - tenho que remover explicitamente a declaração dessas operações da
private:
seção se eu quiser que o compilador possa gerá-las para mim.fonte
Foo(const Foo&) = delete; // no copy constructor
eFoo& Foo=(const Foo&) = delete; // no assignment operator
struct
, mas desejo que eleclass
se comporte de maneira diferente (e sã). No processo, isso também daria uma diferença mais significativa entrestruct
eclass
ao lado do acesso padrão.operator==
. Neste ponto, é apenas açúcar de sintaxe para algum código de placa de caldeira. Se você tem medo de que dessa maneira o programador possa ignorar algum ponteiro entre os campos de classe, você pode adicionar uma condição de que ele só funcione em tipos e objetos primitivos que possuem operadores de igualdade. Não há razão para não permitir isso inteiramente.Mesmo em C ++ 20, o compilador ainda não gera implicitamente
operator==
para vocêMas você terá a capacidade de usar o padrão explicitamente
==
desde C ++ 20 :A padronização
==
é feita em membro==
(da mesma maneira que o construtor de cópia padrão faz a construção de cópia em membro). As novas regras também fornecem o relacionamento esperado entre==
e!=
. Por exemplo, com a declaração acima, eu posso escrever os dois:Esse recurso específico (padrão
operator==
e simetria entre==
e!=
) vem de uma proposta que fazia parte do recurso de idioma mais amplo que éoperator<=>
.fonte
= default
, para algo que não é criado por padrão, certo? Parece oxímoro para mim ("padrão explícito").IMHO, não há razão "boa". A razão pela qual tantas pessoas concordam com essa decisão de design é porque elas não aprenderam a dominar o poder da semântica baseada em valor. As pessoas precisam escrever muitos construtores de cópias personalizadas, operadores de comparação e destruidores, porque usam ponteiros brutos em sua implementação.
Ao usar ponteiros inteligentes apropriados (como std :: shared_ptr), o construtor de cópia padrão geralmente é bom e a implementação óbvia do operador de comparação padrão hipotético seria muito bem.
fonte
É respondido que C ++ não fez == porque C não fez, e aqui está porque C fornece apenas padrão = mas não == em primeiro lugar. C queria mantê-lo simples: C implementado = por memcpy; no entanto, == não pode ser implementado pelo memcmp devido ao preenchimento. Como o preenchimento não é inicializado, o memcmp diz que são diferentes, mesmo que sejam os mesmos. O mesmo problema existe para a classe vazia: o memcmp diz que são diferentes porque o tamanho das classes vazias não é zero. Pode-se ver acima que implementar == é mais complicado que implementar = em C. Alguns exemplos de código a respeito disso. Sua correção é apreciada se eu estiver errado.
fonte
operator=
- isso funcionaria apenas para os tipos de POD, mas o C ++ também fornece um padrãooperator=
para os tipos não de POD.Neste vídeo, Alex Stepanov, o criador do STL, aborda essa questão por volta das 13:00. Para resumir, tendo observado a evolução do C ++, ele argumenta que:
Ele então diz que no futuro (distante) == e ! = Serão implicitamente gerados.
fonte
O C ++ 20 fornece uma maneira de implementar facilmente um operador de comparação padrão.
Exemplo de cppreference.com :
fonte
Point
como exemplo para uma ordenação operação, uma vez que não há nenhuma maneira padrão razoável ordem dois pontos comx
ey
coordenadas ...std::set
para garantir que todos os pontos sejam exclusivos estd::set
utilizemoperator<
apenas.auto
: Para este caso podemos sempre supor que serástd::strong_ordering
a partir de#include <compare>
?std::common_comparison_category_t
, que para esta classe se torna o pedido padrão (std::strong_ordering
).Não é possível definir o padrão
==
, mas você pode definir o padrão!=
através do==
qual você normalmente deve se definir. Para isso, você deve fazer o seguinte:Você pode ver http://www.cplusplus.com/reference/std/utility/rel_ops/ para obter detalhes.
Além disso, se você definir
operator<
, os operadores para <=,>,> = poderão ser deduzidos dele ao usarstd::rel_ops
.Mas você deve ter cuidado ao usar,
std::rel_ops
porque os operadores de comparação podem ser deduzidos para os tipos que você não espera.A maneira mais preferida de deduzir o operador relacionado do básico é usar os operadores boost :: .
A abordagem usada no aumento é melhor porque define o uso do operador para a classe que você deseja apenas, não para todas as classes no escopo.
Você também pode gerar "+" de "+ =", - de "- =", etc ... (veja a lista completa aqui )
fonte
!=
depois de escrever o==
operador. Ou eu fiz, mas estava faltandoconst
ness. Também tive que escrever e tudo estava bem.rel_ops
pela qual foi descontinuado no C ++ 20: porque ele não funciona , pelo menos não em todos os lugares e certamente não de forma consistente. Não existe uma maneira confiávelsort_decreasing()
de compilar. Por outro lado, o Boost.Operators trabalha e sempre trabalhou.C ++ 0x
temtido uma proposta de funções padrão, então você poderia dizerdefault operator==;
Aprendemos que ele ajuda a fazer essas coisas explícito.fonte
operator==
. O que é uma pena.Conceitualmente, não é fácil definir igualdade. Mesmo para dados POD, pode-se argumentar que, mesmo que os campos sejam iguais, mas é um objeto diferente (em um endereço diferente), não é necessariamente igual. Na verdade, isso depende do uso do operador. Infelizmente, seu compilador não é psíquico e não pode inferir isso.
Além disso, as funções padrão são excelentes maneiras de dar um tiro no próprio pé. Os padrões que você descreve estão basicamente lá para manter a compatibilidade com as estruturas de POD. No entanto, eles causam estragos mais do que suficiente, com os desenvolvedores esquecendo-os ou a semântica das implementações padrão.
fonte
int
criado via copiador de outro é igual àquele a partir do qual foi criado; a única coisa lógica a fazer para umstruct
dos doisint
campos é trabalhar exatamente da mesma maneira.+
operador, pois não é associativo para carros alegóricos; isto é(x + y) + z
! =x + (y + z)
, devido à maneira como ocorre o arredondamento FP. (Indiscutivelmente, esse é um problema muito pior do que==
porque é verdadeiro para valores numéricos normais.) Você pode sugerir a adição de um novo operador de adição que funcione para todos os tipos numéricos (mesmo int) e é quase exatamente o mesmo+
que é associativo ( de alguma forma). Mas então você adicionaria inchaço e confusão ao idioma sem realmente ajudar tantas pessoas.Pode não ser um problema funcional, mas em termos de desempenho, a comparação padrão por membro é passível de ser menos otimizada do que a atribuição / cópia padrão por membro. Diferentemente da ordem de atribuição, a ordem de comparação afeta o desempenho porque o primeiro membro desigual implica que o restante pode ser ignorado. Portanto, se existem alguns membros que geralmente são iguais, você deseja compará-los por último e o compilador não sabe quais são os membros com maior probabilidade de serem iguais.
Considere este exemplo, em que
verboseDescription
uma sequência longa é selecionada de um conjunto relativamente pequeno de possíveis descrições meteorológicas.(É claro que o compilador teria o direito de desconsiderar a ordem das comparações se reconhecer que elas não têm efeitos colaterais, mas, presumivelmente, ainda assim retiraria sua que do código-fonte, onde não possui informações melhores.)
fonte
Apenas para que as respostas a esta pergunta permaneçam completas com o passar do tempo: desde C ++ 20, ele pode ser gerado automaticamente com o comando
auto operator<=>(const foo&) const = default;
Ele irá gerar todos os operadores: ==,! =, <, <=,> E> =, consulte https://en.cppreference.com/w/cpp/language/default_comparisons para obter detalhes.
Devido à aparência do operador
<=>
, é chamado de operador de nave espacial. Veja também Por que precisamos do operador da nave espacial <=> em C ++? .EDIT: também em C ++ 11 um belo substituto puro para que está disponível com
std::tie
ver https://en.cppreference.com/w/cpp/utility/tuple/tie para um exemplo de código completo combool operator<(…)
. A parte interessante, alterada para trabalhar,==
é:std::tie
funciona com todos os operadores de comparação e é completamente otimizado pelo compilador.fonte
Concordo que, para as classes do tipo POD, o compilador pode fazer isso por você. No entanto, o que você pode considerar simples, o compilador pode estar errado. Portanto, é melhor deixar o programador fazer isso.
Eu já tive um caso de POD em que dois campos eram únicos - portanto, uma comparação nunca seria considerada verdadeira. No entanto, a comparação que eu precisava apenas comparou na carga útil - algo que o compilador nunca entenderia ou poderia descobrir por si próprio.
Além disso - eles não demoram muito para escrever, não são ?!
fonte