Jestem wielkim fanem pozwalania kompilatorowi na wykonanie dla ciebie jak największej pracy. Pisząc prostą klasę, kompilator może dać ci następujące za „za darmo”:
- Domyślny (pusty) konstruktor
- Konstruktor kopii
- Destruktor
- Operator przypisania (
operator=
)
Ale wydaje się, że nie daje żadnych operatorów porównania - takich jak operator==
lub operator!=
. Na przykład:
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
{ }
Czy jest na to dobry powód? Dlaczego przeprowadzanie porównania poszczególnych członków byłoby problemem? Oczywiście, jeśli klasa przydzieli pamięć, to powinieneś być ostrożny, ale dla prostej klasy z pewnością kompilator mógłby to zrobić za Ciebie?
==
, podobnie jak w przypadku domyślnych automatycznych przypisań (=
). (Argument o wskaźnikach jest niespójna, ponieważ logika ma zastosowanie zarówno do=
i==
, a nie tylko na sekundę).Odpowiedzi:
Kompilator nie będzie wiedział, czy chcesz porównanie wskaźnika czy głębokie (wewnętrzne) porównanie.
Bezpieczniej jest po prostu nie wdrożyć go i pozwolić programiście zrobić to sam. Następnie mogą dokonać wszystkich założeń, które im się podobają.
źródło
operator=
) ogólnie pracy w tym samym kontekście jak operatorów porównania - to znaczy, że jest oczekiwanie, że po wykonaniua = b
,a == b
jest prawdą. Zdecydowanie sensowne jest, aby kompilator zapewnił wartość domyślnąoperator==
przy użyciu tej samej semantyki wartości zagregowanych, jak w przypadkuoperator=
. Podejrzewam, że paercebal ma tutaj rację, ponieważoperator=
(i kopiuj ctor) są dostarczane wyłącznie dla kompatybilności z C i nie chcieli pogorszyć sytuacji.Argument, że jeśli kompilator może zapewnić domyślny konstruktor kopii, powinien być w stanie zapewnić podobny domyślny konstruktor
operator==()
ma pewien sens. Myślę, że powód podjęcia decyzji o niedostarczeniu generatora kompilatora dla tego operatora można odgadnąć na podstawie tego, co Stroustrup powiedział o domyślnym konstruktorze kopii w „The Design and Evolution of C ++” (Rozdział 11.4.1 - Kontrola kopiowania) :Zamiast „dlaczego C ++ nie ma wartości domyślnej
operator==()
?”, Pytanie powinno brzmieć „dlaczego C ++ ma domyślny konstruktor przypisania i kopiowania?”, Przy czym odpowiedź jest taka, że Stroustrup niechętnie uwzględnił te elementy w celu zapewnienia wstecznej zgodności z C (prawdopodobnie przyczyną większości brodawek C ++, ale także prawdopodobnie główną przyczyną popularności C ++).Na własne potrzeby w moim IDE fragment kodu, którego używam dla nowych klas, zawiera deklaracje dla prywatnego operatora przypisania i konstruktora kopiowania, więc gdy generuję nową klasę, nie otrzymuję żadnych domyślnych operacji przypisania i kopiowania - muszę jawnie usunąć deklarację tych operacji z
private:
sekcji, jeśli chcę, aby kompilator mógł je dla mnie wygenerować.źródło
Foo(const Foo&) = delete; // no copy constructor
iFoo& Foo=(const Foo&) = delete; // no assignment operator
struct
, ale chciałbym, abyclass
zachowywał się inaczej (i zdrowo). W tym procesie dałoby to również bardziej znaczącą różnicę między domyślnym dostępemstruct
aclass
dostępem obok niego.operator==
. W tym momencie jest to tylko cukier składniowy dla jakiegoś kodu płyty kotłowej. Jeśli obawiasz się, że w ten sposób programista może przeoczyć jakiś wskaźnik wśród pól klas, możesz dodać warunek, że może on działać tylko na prymitywnych typach i obiektach, które same mają operatory równości. Nie ma jednak powodu, aby całkowicie to odrzucać.Nawet w C ++ 20 kompilator nadal nie będzie generował
operator==
dla ciebie domyślnieAle zyskasz możliwość jawnego domyślnego działania
==
od C ++ 20 :Domyślne ustawienie
==
działa w zależności od członka==
(w taki sam sposób, jak domyślny konstruktor kopiuje w przypadku tworzenia kopii w zależności od członka). Nowe zasady zapewniają również oczekiwany związek między==
i!=
. Na przykład dzięki powyższej deklaracji mogę napisać oba:Ta szczególna funkcja (domyślna
operator==
i symetria pomiędzy==
i!=
) pochodzi z jednej propozycji, która była częścią szerszej funkcji języka, którą jestoperator<=>
.źródło
= default
, dla rzeczy, która nie jest tworzona domyślnie, prawda? Dla mnie to brzmi jak oksymoron („wyraźne domyślne”).IMHO, nie ma „dobrego” powodu. Powodem, dla którego tak wielu ludzi zgadza się z tą decyzją projektową, jest to, że nie nauczyli się opanowywać potęgi semantyki opartej na wartościach. Ludzie muszą napisać wiele niestandardowych konstruktorów kopiowania, operatorów porównania i destruktorów, ponieważ używają surowych wskaźników w swojej implementacji.
Przy stosowaniu odpowiednich inteligentnych wskaźników (takich jak std :: shared_ptr) domyślny konstruktor kopii jest zwykle w porządku, a oczywista implementacja hipotetycznego domyślnego operatora porównania byłaby równie dobra.
źródło
Odpowiedź: C ++ nie zrobił ==, ponieważ C nie, a oto dlaczego C zapewnia tylko domyślną =, ale nie == na pierwszym miejscu. C chciał uprościć: C zaimplementowane = przez memcpy; jednak == nie może zostać zaimplementowane przez memcmp z powodu wypełniania. Ponieważ padding nie jest inicjowany, memcmp mówi, że są różne, mimo że są takie same. Ten sam problem istnieje w przypadku pustych klas: memcmp mówi, że są one różne, ponieważ wielkość pustych klas nie jest równa zero. Z góry widać, że implementacja == jest bardziej skomplikowana niż implementacja = w C. Niektóre przykłady kodu dotyczące tego. Twoja poprawka jest mile widziana, jeśli się mylę.
źródło
operator=
- działałoby to tylko dla typów POD, ale C ++ zapewnia domyślną równieżoperator=
dla typów innych niż POD.W tym filmie Alex Stepanov, twórca STL, odpowiada na to pytanie około godziny 13:00. Podsumowując, obserwując ewolucję C ++, twierdzi, że:
Następnie mówi, że w (odległej) przyszłości == i ! = Zostaną domyślnie wygenerowane.
źródło
C ++ 20 zapewnia sposób na łatwą implementację domyślnego operatora porównania.
Przykład z cppreference.com :
źródło
Point
za przykład operacji zamawiania , ponieważ nie ma rozsądnego domyślnego sposobu zamówienia dwóch punktówx
iy
współrzędnych ...std::set
aby upewnić się, że wszystkie punkty są unikalne istd::set
używaoperator<
tylko.auto
: W takim przypadku zawsze możemy założyć, że będzie onstd::strong_ordering
pochodzić#include <compare>
?std::common_comparison_category_t
, który dla tej klasy staje się domyślną kolejnością (std::strong_ordering
).Nie można zdefiniować wartości domyślnej
==
, ale można zdefiniować wartość domyślną,!=
za pomocą==
której zwykle należy się zdefiniować. W tym celu należy wykonać następujące czynności:Szczegółowe informacje można znaleźć na stronie http://www.cplusplus.com/reference/std/utility/rel_ops/ .
Ponadto, jeśli zdefiniujesz
operator<
, operatory dla <=,>,> = można z niego wywnioskować podczas używaniastd::rel_ops
.Należy jednak zachować ostrożność podczas korzystania,
std::rel_ops
ponieważ operatory porównania można wywnioskować dla typów, których się nie oczekuje.Bardziej preferowanym sposobem na wyciągnięcie powiązanego operatora z podstawowego jest użycie boost :: operators .
Podejście zastosowane w boost jest lepsze, ponieważ definiuje użycie operatora dla klasy, którą chcesz, a nie dla wszystkich klas w zakresie.
Możesz również wygenerować „+” z „+ =”, - z „- =” itd. (Zobacz pełną listę tutaj )
źródło
!=
po napisaniu==
operatora. Albo ja to zrobiłem, ale brakowało jejconst
. Sam też musiałem to napisać i wszystko było dobrze.rel_ops
C ++ 20 jest nieaktualny powód : ponieważ nie działa , przynajmniej nie wszędzie, a na pewno nie konsekwentnie. Nie ma niezawodnego sposobusort_decreasing()
na kompilację. Z drugiej strony Boost.Operators działa i zawsze działał.C ++ 0x
mapropozycję domyślnych funkcji, więc można powiedzieć,default operator==;
że dowiedzieliśmy się, że pomaga to wyjaśnić te rzeczy.źródło
operator==
. Szkoda.Koncepcyjnie nie jest łatwo zdefiniować równość. Nawet w przypadku danych POD można argumentować, że nawet jeśli pola są takie same, ale jest to inny obiekt (pod innym adresem), niekoniecznie jest on równy. To zależy od sposobu użytkowania operatora. Niestety twój kompilator nie jest medium i nie może tego wywnioskować.
Poza tym domyślne funkcje to doskonały sposób na zastrzelenie się w stopę. Opisane wartości domyślne mają zasadniczo na celu zachowanie zgodności ze strukturami POD. Powodują one jednak więcej niż wystarczające spustoszenie, gdy programiści zapominają o nich lub semantykę domyślnych implementacji.
źródło
int
utworzony za pomocą kopiowania ctor z drugiego jest równy temu, z którego został utworzony; jedyną logiczną rzeczą dla jednegostruct
z dwóchint
pól jest działanie dokładnie w ten sam sposób.+
operatora, ponieważ nie jest on skojarzony z liczbami zmiennoprzecinkowymi ; to znaczy(x + y) + z
! =x + (y + z)
, ze względu na sposób zaokrąglania FP. (Prawdopodobnie jest to o wiele gorszy problem niż==
dlatego, że dotyczy to normalnych wartości liczbowych). Możesz zasugerować dodanie nowego operatora dodawania, który działa dla wszystkich typów liczbowych (nawet int) i jest prawie dokładnie taki sam,+
ale jest skojarzony ( jakoś). Ale wtedy dodawalibyście wzdęci i zamieszania do języka, nie pomagając tak wielu ludziom.Może to nie być problem funkcjonalnie, ale pod względem wydajności domyślne porównanie poszczególnych członków może być bardziej nieoptymalne niż domyślne przypisanie / kopiowanie poszczególnych członków. W przeciwieństwie do kolejności przypisywania, kolejność porównywania wpływa na wydajność, ponieważ pierwszy nierówny element oznacza, że resztę można pominąć. Więc jeśli istnieją elementy, które są zwykle równe, chcesz je porównać na końcu, a kompilator nie wie, które elementy są bardziej prawdopodobne.
Rozważ ten przykład, gdzie
verboseDescription
jest długi ciąg wybrany ze stosunkowo niewielkiego zestawu możliwych opisów pogody.(Oczywiście kompilator byłby uprawniony do zignorowania kolejności porównań, gdyby rozpoznał, że nie mają one żadnych skutków ubocznych, ale przypuszczalnie nadal pobierałby swoją kolejkę od kodu źródłowego, w którym nie ma lepszych informacji).
źródło
Wystarczy, że odpowiedzi na to pytanie pozostaną kompletne w miarę upływu czasu: od C ++ 20 można go automatycznie wygenerować za pomocą polecenia
auto operator<=>(const foo&) const = default;
Wygeneruje wszystkie operatory: ==,! =, <, <=,> I> =, patrz https://en.cppreference.com/w/cpp/language/default_comparisons celu uzyskania szczegółowych informacji.
Ze względu na wygląd operatora
<=>
nazywany jest operatorem statku kosmicznego. Zobacz także Dlaczego potrzebujemy operatora statku kosmicznego <=> w C ++?.EDIT: również w C ++ 11 całkiem schludny substytutem, który jest dostępny z
std::tie
zobaczyć https://en.cppreference.com/w/cpp/utility/tuple/tie dla kompletnego przykład kodu zbool operator<(…)
. Interesującą częścią, zmienioną do pracy,==
jest:std::tie
działa ze wszystkimi operatorami porównania i jest całkowicie zoptymalizowany przez kompilator.źródło
Zgadzam się, w przypadku klas typu POD kompilator mógłby zrobić to za Ciebie. Jednak to, co możesz uznać za proste, kompilator może się nie powieść. Lepiej więc pozwolić programiście to zrobić.
Miałem kiedyś przypadek POD, w którym dwa pola były unikalne - więc porównanie nigdy nie będzie uważane za prawdziwe. Jednak porównanie, którego potrzebowałem, porównywałem tylko ładunek - coś, czego kompilator nigdy nie zrozumie lub nigdy nie będzie w stanie samodzielnie zrozumieć.
Poza tym - nie trzeba długo pisać, prawda ?!
źródło