Uwaga: odpowiedzi udzielono w określonej kolejności , ale ponieważ wielu użytkowników sortuje odpowiedzi według głosów, a nie czasu, w którym zostały udzielone, oto indeks odpowiedzi w kolejności, w której mają one największy sens:
- Ogólna składnia przeciążenia operatora w C ++
- Trzy podstawowe zasady przeciążania operatora w C ++
- Decyzja między członkiem a podmiotem niebędącym członkiem
- Często operatorzy przeciążają
- Operator przypisania
- Operatory wejściowe i wyjściowe
- Operator wywołania funkcji
- Operatory porównania
- Operatory arytmetyczne
- Subskrybowanie tablicy
- Operatory dla typów podobnych do wskaźników
- Operatorzy konwersji
- Przeładowanie nowego i usunięcie
(Uwaga: ma to być wpis do często zadawanych pytań na temat C ++ w programie Stack Overflow . Jeśli chcesz skrytykować pomysł podania w tym formularzu odpowiedzi na najczęściej zadawane pytania, to miejsce na publikację na meta, które to wszystko rozpoczęło, byłoby odpowiednim miejscem. Odpowiedzi na to pytanie jest monitorowane w czacie C ++ , gdzie pomysł FAQ powstał w pierwszej kolejności, więc twoje odpowiedzi prawdopodobnie zostaną przeczytane przez tych, którzy wpadli na ten pomysł).
Odpowiedzi:
Często operatorzy przeciążają
Większość pracy operatorów przeciążających polega na kodowaniu płyt kotłowych. Nic dziwnego, skoro operatory są jedynie cukrem syntaktycznym, ich faktyczną pracę można wykonać (i często przekazuje się) zwykłym funkcjom. Ale ważne jest, aby dobrze zrozumieć ten kod kotła. Jeśli się nie powiedzie, albo kod twojego operatora się nie skompiluje, albo kod twoich użytkowników nie skompiluje się, albo kod twoich użytkowników będzie działał zaskakująco.
Operator przypisania
Wiele można powiedzieć o przydziale. Jednak większość z nich została już powiedziana w słynnym często zadawanym pytaniu na temat kopiowania i wymiany GMan , więc pominę większość z nich tutaj, podając jedynie operatora idealnego przypisania:
Operatory Bitshift (używane do strumieniowego we / wy)
Operatory bitshift
<<
i>>
chociaż nadal używane w sprzęcie sprzętowym dla funkcji manipulacji bitami, które dziedziczą po C, stały się bardziej powszechne jako przeciążone operatory wejścia i wyjścia strumienia w większości aplikacji. Aby uzyskać wskazówki dotyczące przeciążania jako operatorów manipulacji bitami, zobacz sekcję poniżej na temat binarnych operatorów arytmetycznych. Aby wdrożyć własny format niestandardowy i logikę analizowania, gdy obiekt jest używany z iostreams, kontynuuj.Operatory strumieniowe, wśród najczęściej przeciążonych operatorów, są operatorami binarnych poprawek, dla których składnia nie określa, czy powinny być członkami, czy nie. Ponieważ zmieniają swój lewy argument (zmieniają stan strumienia), powinny, zgodnie z praktycznymi zasadami, być implementowane jako członkowie typu ich lewego operandu. Jednak ich lewe operandy są strumieniami ze standardowej biblioteki i chociaż większość operatorów wyjściowych i wejściowych strumienia zdefiniowanych przez standardową bibliotekę jest rzeczywiście zdefiniowana jako członkowie klas strumieniowych, kiedy implementujesz operacje wyjściowe i wejściowe dla własnych typów, nie można zmienić typów strumieni biblioteki standardowej. Dlatego musisz zaimplementować te operatory dla własnych typów jako funkcje nie będące członkami. Kanoniczne formy tych dwóch są następujące:
Podczas implementacji
operator>>
ręczne ustawienie stanu strumienia jest konieczne tylko wtedy, gdy sam odczyt się powiedzie, ale wynik nie jest zgodny z oczekiwaniami.Operator wywołania funkcji
Operator wywołanie funkcji, stosuje się do tworzenia obiektów funkcyjnych, znane również jako funktorów należy zdefiniować jako człon funkcji, tak że zawsze jest niejawnie
this
argument funkcji składowych. Poza tym może być przeciążony, aby przyjąć dowolną liczbę dodatkowych argumentów, w tym zero.Oto przykład składni:
Stosowanie:
W całej standardowej bibliotece C ++ obiekty funkcyjne są zawsze kopiowane. Dlatego twoje własne obiekty funkcyjne powinny być tanie do kopiowania. Jeśli obiekt funkcji musi bezwzględnie wykorzystywać dane, których kopiowanie jest kosztowne, lepiej jest przechowywać te dane gdzie indziej i odwoływać się do obiektu funkcji.
Operatory porównania
Binarne operatory porównania przyrostków powinny być, zgodnie z ogólnymi zasadami, zaimplementowane jako funkcje nie będące członkami 1 . Negacja pojedynczego prefiksu
!
powinna (zgodnie z tymi samymi regułami) zostać zaimplementowana jako funkcja członka. (ale przeładowanie go zwykle nie jest dobrym pomysłem).Algorytmy biblioteki standardowej (np.
std::sort()
) I typy (np.std::map
) Zawsze będą oczekiwać tylkooperator<
obecności. Jednak użytkownicy tego typu będą oczekiwać, że wszyscy inni operatorzy również będą obecni , więc jeśli zdefiniujeszoperator<
, pamiętaj o przestrzeganiu trzeciej podstawowej zasady przeciążania operatora, a także zdefiniuj wszystkie inne operatory porównania boolowskiego. Kanoniczny sposób ich wdrożenia jest następujący:Ważną rzeczą do odnotowania tutaj jest to, że tylko dwóch z tych operatorów faktycznie robi cokolwiek, inni po prostu przekazują swoje argumenty do jednego z tych dwóch, aby wykonali rzeczywistą pracę.
Składnia przeciążenia pozostałych binarnych operatorów boolowskich (
||
,&&
) jest zgodna z regułami operatorów porównania. Jednak jest bardzo mało prawdopodobne, aby znaleźć uzasadnione zastosowanie dla tych 2 .1 Podobnie jak w przypadku wszystkich podstawowych zasad, czasami mogą istnieć powody, aby je złamać. Jeśli tak, nie zapominaj, że operand po lewej stronie binarnych operatorów porównania, który będzie dla funkcji składowych
*this
, również musi byćconst
. Tak więc operator porównania zaimplementowany jako funkcja członka musiałby mieć ten podpis:(Uwaga
const
na końcu.)2 Należy zauważyć, że wbudowana wersja semantyki skrótu
||
i jej&&
użycie. Podczas gdy te zdefiniowane przez użytkownika (ponieważ są cukrami składniowymi dla wywołań metod), nie używają semantyki skrótów. Użytkownik będzie oczekiwać, że operatorzy będą mieli semantykę skrótów, a ich kod może od tego zależeć, dlatego NIGDY nie zaleca się ich definiowania.Operatory arytmetyczne
Jednoargumentowe operatory arytmetyczne
Jednostkowe operatory inkrementacji i dekrementacji mają zarówno przedrostek, jak i postfiks. Aby odróżnić jeden od drugiego, warianty Postfiksa wymagają dodatkowego argumentu typu dummy int. Jeśli przeciążasz przyrost lub spadek, pamiętaj, aby zawsze implementować zarówno wersję przedrostkową, jak i późniejszą. Oto kanoniczna implementacja przyrostu, dekrementacja przebiega według tych samych zasad:
Zauważ, że wariant Postfiksa jest zaimplementowany pod względem prefiksu. Pamiętaj również, że postfix robi dodatkową kopię. 2)
Przeciążenie jednoargumentowego minus i plus nie jest zbyt powszechne i prawdopodobnie najlepiej go unikać. W razie potrzeby prawdopodobnie powinny być przeciążone jako funkcje składowe.
2 Zauważ też, że wariant postfiksowy działa więcej i dlatego jest mniej wydajny w użyciu niż wariant prefiksowy. Jest to dobry powód, aby ogólnie preferować przyrost prefiksu od przyrostu postfiksu. Podczas gdy kompilatory zwykle optymalizują dodatkową pracę przyrostu postfiksów dla typów wbudowanych, mogą nie być w stanie zrobić tego samego dla typów zdefiniowanych przez użytkownika (co może być czymś tak niewinnie wyglądającym jak iterator listy). Kiedy już się przyzwyczaisz
i++
, bardzo trudno jest pamiętać o zrobieniu++i
tego, gdyi
nie jest on wbudowanym typem (plus trzeba zmienić kod przy zmianie typu), więc lepiej jest nawyk zawsze używając przyrostka prefiksu, chyba że postfiks jest wyraźnie potrzebny.Binarne operatory arytmetyczne
W przypadku binarnych operatorów arytmetycznych nie zapomnij przestrzegać trzeciej podstawowej zasady przeciążania operatora: jeśli podasz
+
, podaj także+=
, jeśli podasz-
, nie pomiń-=
itp. Mówi się, że Andrew Koenig jako pierwszy zauważył, że przypisanie złożone operatory mogą być używane jako baza dla ich nieskomplikowanych odpowiedników. Oznacza to, że operator+
jest wdrażany pod względem+=
,-
jest wdrażany pod względem-=
itp.Zgodnie z naszymi praktycznymi zasadami,
+
a jego towarzysze powinni być członkami niebędącymi członkami, a ich odpowiedniki przypisania złożonego (+=
itp.), Zmieniając lewy argument, powinni być członkami. Oto przykładowy kod dla+=
i+
; inne binarne operatory arytmetyczne powinny być implementowane w ten sam sposób:operator+=
zwraca wynik według odwołania, aoperator+
zwraca kopię wyniku. Oczywiście, zwracanie referencji jest zwykle bardziej wydajne niż zwracanie kopii, ale w przypadkuoperator+
kopiowania nie ma mowy. Kiedy piszesza + b
, oczekujesz, że wynik będzie nową wartością, dlatego musiszoperator+
zwrócić nową wartość. 3 Zauważ też, żeoperator+
lewy operand pobiera kopię, a nie stałą. Powód tego jest taki sam jak powód podaniaoperator=
argumentu na kopię.Operatory manipulacji bitami
~
&
|
^
<<
>>
powinny być implementowane w taki sam sposób, jak operatory arytmetyczne. Jednak (z wyjątkiem przeciążenia<<
oraz>>
danych wyjściowych i wejściowych) istnieje bardzo niewiele uzasadnionych przypadków użycia w przypadku ich przeciążenia.3 Ponownie, lekcja, którą należy z tego wyciągnąć,
a += b
jest na ogół bardziej wydajna niża + b
i powinna być preferowana, jeśli to możliwe.Subskrybowanie tablicy
Operator indeksu tablicy jest operatorem binarnym, który musi być zaimplementowany jako członek klasy. Służy do typów podobnych do kontenerów, które umożliwiają dostęp do ich elementów danych za pomocą klucza. Kanoniczna forma ich dostarczenia jest następująca:
O ile nie chcesz, aby użytkownicy twojej klasy mogli zmieniać elementy danych zwracane przez
operator[]
(w takim przypadku możesz pominąć wariant inny niż const), zawsze powinieneś podać oba warianty operatora.Jeśli wiadomo, że typ_wartości odnosi się do typu wbudowanego, wariant const operatora powinien lepiej zwrócić kopię zamiast odwołania const:
Operatory dla typów podobnych do wskaźników
Aby zdefiniować własne iteratory lub inteligentne wskaźniki, musisz przeciążyć jednoargumentowy operator dereferencji prefiksu
*
i operator dostępu do wskaźnika wskaźnika binarnego->
:Zauważ, że one również prawie zawsze będą wymagały zarówno wersji const, jak i wersji non-const. Dla
->
operatora, jeślivalue_type
jest typuclass
(lubstruct
lubunion
), innyoperator->()
jest wywoływany rekurencyjnie, dopóki nieoperator->()
zwróci wartości typu nieklasowego.Jednostkowy adres operatora nigdy nie powinien być przeciążony.
Dla
operator->*()
zobaczyć to pytanie . Jest rzadko używany, a zatem rzadko przeciążony. W rzeczywistości nawet iteratory go nie przeciążają.Przejdź do Operatorów konwersji
źródło
operator->()
jest naprawdę niesamowicie dziwny. Zwrócenie a nie jest wymaganevalue_type*
- w rzeczywistości może zwrócić inny typ klasy, pod warunkiem, że ma on typoperator->()
, który zostanie następnie wywołany. To rekurencyjne wywoływanieoperator->()
s odbywa się do momentu wystąpieniavalue_type*
typu zwracanego. Szaleństwo! :)*
w kategoriach,*=
ale byłoby to niezręczne, ponieważ jedna z pierwszych operacji*=
utworzyłaby nowy obiekt, wynik obliczeń. Następnie po pętli for-ijk zamienilibyśmy ten obiekt tymczasowy za pomocą*this
. to znaczy. 1. kopia, 2. operator *, 3. zamianaT* const
zwracali aconst T&
przy dereferencji, co nie jest prawdą. Lub innymi słowy: wskaźnik const nie implikuje const pointee. W rzeczywistości naśladowanie nie jest trywialneT const *
- co jest przyczyną wszystkichconst_iterator
rzeczy w standardowej bibliotece. Wniosek: podpis powinien byćreference_type operator*() const; pointer_type operator->() const
L <= R
można również wyrazić jako!(R < L)
zamiast!(L > R)
. Może zaoszczędzić dodatkową warstwę wstawiania w trudnych do optymalizacji wyrażeniach (i tak też implementuje to Boost.Operators).Trzy podstawowe zasady przeciążania operatora w C ++
Jeśli chodzi o przeciążanie operatorów w C ++, należy przestrzegać trzech podstawowych zasad . Podobnie jak w przypadku wszystkich takich zasad, istnieją wyjątki. Czasami ludzie odstępują od nich, a wynikiem nie jest zły kod, ale takich pozytywnych odchyleń jest niewiele. Przynajmniej 99 na 100 takich odchyleń, które widziałem, było nieuzasadnionych. Jednak równie dobrze mogło to być 999 na 1000. Więc lepiej trzymaj się następujących zasad.
Ilekroć znaczenie operatora nie jest oczywiście jasne i niekwestionowane, nie należy go przeciążać. Zamiast tego podaj funkcję o dobrze wybranej nazwie.
Zasadniczo, pierwsza i najważniejsza zasada przeciążania operatorów, w samym jej sercu, mówi: nie rób tego . To może wydawać się dziwne, ponieważ istnieje wiele informacji na temat przeciążania operatorów, a więc wiele artykułów, rozdziałów książek i innych tekstów zajmuje się tym wszystkim. Ale pomimo tych pozornie oczywistych dowodów, istnieje tylko zaskakująco niewiele przypadków, w których przeciążenie operatora jest właściwe . Powodem jest to, że tak naprawdę trudno jest zrozumieć semantykę zastosowania operatora, chyba że użycie operatora w dziedzinie aplikacji jest dobrze znane i niekwestionowane. W przeciwieństwie do powszechnego przekonania, rzadko tak jest.
Zawsze trzymaj się dobrze znanej semantyki operatora.
C ++ nie nakłada żadnych ograniczeń na semantykę przeciążonych operatorów. Twój kompilator chętnie zaakceptuje kod, który implementuje
+
operatorbinarnydo odejmowania od jego prawego operandu. Jednak użytkownicy takiego operatora nigdy nie podejrzewają ekspresjęa + b
odjąća
odb
. Oczywiście zakłada to, że semantyka operatora w domenie aplikacji jest niekwestionowana.Zawsze zapewniaj wszystko z zestawu powiązanych operacji.
Operatorzy są ze sobą powiązani i innymi operacjami. Jeśli Twój typ obsługuje
a + b
, użytkownicy również będą mogli zadzwonića += b
. Jeśli obsługuje inkrementację prefiksu++a
, będą również oczekiwać,a++
że zadziała. Jeśli będą w stanie sprawdzić, czy naa < b
pewno będą oczekiwać, że będą w stanie sprawdzić, czya > b
. Jeśli potrafią skopiować i skonstruować twój typ, oczekują, że przypisanie również zadziała.Przejdź do decyzji między członkiem a członkiem niebędącym członkiem .
źródło
boost::spirit
lol.+
konkatenacji łańcuchów jest naruszeniem, ale teraz stało się już dobrze ustaloną praktyką, przez co wydaje się naturalne. Chociaż pamiętam klasę strun domowych, które widziałem w latach 90., które używały&
do tego celu binarnych (odnosząc się do BASIC dla ustalonych praktyk). Ale tak, włożenie go do standardowej biblioteki lib po prostu osadza w kamieniu. To samo dotyczy nadużyć<<
i>>
IO, BTW. Dlaczego przesunięcie w lewo byłoby oczywistą operacją wyjściową? Ponieważ wszyscy dowiedzieliśmy się o tym, kiedy zobaczyliśmy nasze pierwsze „Cześć, świecie!” podanie. I bez żadnego innego powodu.operator==
jest to, że powinna to być relacja równoważności (IOW, nie powinieneś używać nie sygnalizującego NaN). Istnieje wiele użytecznych relacji równoważności na kontenerach. Co oznacza równość? „a
równa sięb
” oznacza toa
ib
ma tę samą wartość matematyczną. Pojęcie wartości matematycznej (innej niż NaN)float
jest jasne, ale wartość matematyczna kontenera może mieć wiele różnych (rekurencyjnych) przydatnych definicji. Najsilniejsza definicja równości brzmi „są to te same obiekty” i jest bezużyteczna.Ogólna składnia przeciążenia operatora w C ++
Nie można zmienić znaczenia operatorów dla wbudowanych typów w C ++, operatory mogą być przeciążone tylko dla typów zdefiniowanych przez użytkownika 1 . Oznacza to, że co najmniej jeden operand musi być typu zdefiniowanego przez użytkownika. Podobnie jak w przypadku innych przeciążonych funkcji, operatory mogą zostać przeciążone dla określonego zestawu parametrów tylko raz.
Nie wszystkie operatory mogą być przeciążone w C ++. Wśród operatorów, których nie można przeciążać, są:
.
::
sizeof
typeid
.*
i jedyny trójskładnikowy operator w C ++,?:
Wśród operatorów, które mogą być przeciążone w C ++ są:
+
-
*
/
%
i+=
-=
*=
/=
%=
(wszystkie dwójki);+
-
(unarny prefiks);++
--
(jednorazowy prefiks i postfiks)&
|
^
<<
>>
i&=
|=
^=
<<=
>>=
(wszystkie binarne poprawki);~
(jednorazowy prefiks)==
!=
<
>
<=
>=
||
&&
(wszystkie binarne infix);!
(jednorazowy prefiks)new
new[]
delete
delete[]
=
[]
->
->*
,
(wszystkie binarne infix);*
&
(wszystkie jednoargumentowe)()
(wywołanie funkcji, n-aryfiks)Jednak fakt, że możesz przeciążać je wszystkie, nie oznacza, że powinieneś to zrobić. Zobacz podstawowe zasady przeciążania operatora.
W C ++ operatory są przeciążone w postaci funkcji o specjalnych nazwach . Podobnie jak w przypadku innych funkcji, przeciążone operatory można zasadniczo zaimplementować albo jako funkcję składową typu ich lewego operandu, albo jako funkcje nie będące członkami . To, czy masz swobodę wyboru, czy jedno z nich zależy, zależy od kilku kryteriów. 2 Jednoargumentowy operator
@
3 , zastosowany do obiektu x, jest wywoływany jakooperator@(x)
lub jakox.operator@()
. Operator binarnej poprawki@
, zastosowany do obiektówx
iy
, jest wywoływany jakooperator@(x,y)
lub jakox.operator@(y)
. 4Operatory, które są implementowane jako funkcje nie będące członkami, są czasami przyjacielami typu operandu.
1 Termin „zdefiniowany przez użytkownika” może być nieco mylący. C ++ rozróżnia typy wbudowane od typów zdefiniowanych przez użytkownika. Do tych pierwszych należą na przykład int, char i double; do tych ostatnich należą wszystkie typy struct, class, union i enum, w tym te ze standardowej biblioteki, mimo że jako takie nie są zdefiniowane przez użytkowników.
2 Jest to omówione w dalszej części tego FAQ.
3 Nie
@
jest poprawnym operatorem w C ++, dlatego używam go jako symbolu zastępczego.4 Jedynego operatora trójskładnikowego w C ++ nie można przeciążać, a jedyny operator n-ary musi zawsze być implementowany jako funkcja składowa.
Przejdź do trzech podstawowych zasad przeciążania operatora w C ++ .
źródło
~
jest przedrostkiem jednoargumentowym, a nie dwójkowym..*
brakuje na liście operatorów nieobciążalnych.:)
operator+()
jako funkcję członka, ale nadał mu podpis funkcji bezpłatnej. Zobacz tutaj .Decyzja między członkiem a podmiotem niebędącym członkiem
Operatory binarne
=
(przypisanie),[]
(subskrypcja tablicowa),->
(dostęp do członka), a także()
operator n-ary (wywołanie funkcji), muszą być zawsze implementowane jako funkcje składowe , ponieważ wymaga tego składnia języka.Inni operatorzy mogą być implementowani jako członkowie lub jako członkowie niebędący członkami. Niektóre z nich jednak zwykle muszą być zaimplementowane jako funkcje nie będące członkami, ponieważ ich lewy operand nie może być przez ciebie modyfikowany. Najważniejsze z nich to operatory wejściowe i wyjściowe,
<<
a>>
których lewe operandy to klasy strumienia ze standardowej biblioteki, których nie można zmienić.W przypadku wszystkich operatorów, w których musisz wybrać ich implementację jako funkcję członka lub funkcję nie będącą członkiem, użyj następujących ogólnych zasad, aby zdecydować:
Oczywiście, podobnie jak w przypadku wszystkich zasad, istnieją wyjątki. Jeśli masz typ
i chcesz przeciążyć dla niego operatory inkrementacji i dekrementacji, nie możesz tego robić jako funkcji składowej, ponieważ w C ++ typy wyliczeniowe nie mogą mieć funkcji składowych. Więc musisz go przeciążyć jako bezpłatną funkcję. A
operator<()
dla szablonu klasy zagnieżdżonego w szablonie klasy jest znacznie łatwiej pisać i czytać, gdy jest wykonywany jako funkcja składowa wbudowana w definicję klasy. Ale są to rzeczywiście rzadkie wyjątki.( Jeśli jednak zrobisz wyjątek, nie zapomnij o kwestii
const
-ness dla operandu, który dla funkcji składowych staje się domyślnymthis
argumentem. Jeśli operator jako funkcja nie będąca członkiem wziąłby swój argument znajdujący się najdalej z lewej strony jakoconst
odniesienie , ten sam operator, co funkcja członka, musi miećconst
na końcu*this
znak, aby móc sięconst
odwoływać).Przejdź do wspólnych operatorów, aby przeładować .
źródło
operator+=()
nie bycia członkiem. Musi zmienić operand po lewej stronie, więc z definicji musi kopać głęboko w swoich wnętrznościach. Co zyskałbyś, nie czyniąc go członkiem?operator +=
iappend
metody.append
Metoda jest bardziej kompletna, ponieważ można dołączyć podciąg parametru z indeksu i do indeksu n -1:append(string, start, end)
Wydaje się logiczne, aby mieć+=
rozmowę z appendstart = 0
iend = string.size
. W tym momencie append może być metodą członkowską, aleoperator +=
nie musi być członkiem, a uczynienie go nie-członkiem zmniejszyłoby ilość kodu grającego z dodatkami String, więc to dobrze ... ^ _ ^ ...Operatory konwersji (znane również jako konwersje zdefiniowane przez użytkownika)
W C ++ możesz tworzyć operatory konwersji, które pozwalają kompilatorowi na konwersję między twoimi typami i innymi zdefiniowanymi typami. Istnieją dwa typy operatorów konwersji, jawne i jawne.
Niejawne operatory konwersji (C ++ 98 / C ++ 03 i C ++ 11)
Niejawny operator konwersji umożliwia kompilatorowi niejawną konwersję (podobnie jak konwersję między
int
ilong
) wartości typu zdefiniowanego przez użytkownika na inny typ.Oto prosta klasa z niejawnym operatorem konwersji:
Domniemane operatory konwersji, podobnie jak konstruktory jednoargumentowe, są konwersjami zdefiniowanymi przez użytkownika. Kompilatory udzielą jednej konwersji zdefiniowanej przez użytkownika podczas próby dopasowania wywołania do przeciążonej funkcji.
Na początku wydaje się to bardzo pomocne, ale problem polega na tym, że niejawna konwersja rozpoczyna się nawet wtedy, gdy nie jest to oczekiwane. W poniższym kodzie
void f(const char*)
zostanie wywołany, ponieważmy_string()
nie jest to wartość , więc pierwszy nie pasuje:Początkujący łatwo się mylą, a nawet doświadczeni programiści C ++ są czasem zaskoczeni, ponieważ kompilator wybiera przeciążenie, którego nie podejrzewali. Problemy te można złagodzić dzięki jawnym operatorom konwersji.
Jawne operatory konwersji (C ++ 11)
W przeciwieństwie do niejawnych operatorów konwersji, jawne operatory konwersji nigdy się nie uruchomią, jeśli się ich nie spodziewasz. Oto prosta klasa z jawnym operatorem konwersji:
Zwróć uwagę na
explicit
. Teraz, gdy próbujesz wykonać nieoczekiwany kod od niejawnych operatorów konwersji, pojawia się błąd kompilatora:Aby wywołać jawny operator rzutowania, musisz użyć
static_cast
rzutowania w stylu C lub rzutowania w stylu konstruktora (tjT(value)
.).Jest jednak jeden wyjątek: kompilator może niejawnie przekonwertować się na
bool
. Ponadto kompilatorowi nie wolno wykonywać kolejnej niejawnej konwersji po konwersjibool
(kompilator może wykonywać 2 niejawne konwersje jednocześnie, ale maksymalnie 1 konwersję zdefiniowaną przez użytkownika).Ponieważ kompilator nie rzuci „przeszłości”
bool
, jawne operatory konwersji usuwają teraz potrzebę używania idiomu Safe Bool . Na przykład inteligentne wskaźniki przed C ++ 11 używały idiomu Safe Bool, aby zapobiec konwersji na typy całkowe. W C ++ 11 inteligentne wskaźniki zamiast tego używają jawnego operatora, ponieważ kompilator nie może niejawnie konwertować na typ integralny po jawnej konwersji typu na bool.Kontynuuj do przeciążenia
new
idelete
.źródło
Przeciążenie
new
idelete
Uwaga: dotyczy to tylko składni przeciążenia,
new
adelete
nie implementacji takich przeciążonych operatorów. Myślę, że semantyka przeciążanianew
idelete
zasługuję na własne FAQ , w temacie przeciążania operatora nigdy nie mogę tego oddać sprawiedliwie.Podstawy
W C ++, gdy piszesz nowe wyrażenie, tak jak
new T(arg)
przy ocenie tego wyrażenia, zdarzają się dwie rzeczy: Najpierwoperator new
wywoływana jest pamięć surowa, a następnieT
wywoływany jest odpowiedni konstruktor, aby przekształcić tę surową pamięć w poprawny obiekt. Podobnie, gdy usuwasz obiekt, najpierw wywoływany jest jego destruktor, a następnie pamięć jest przywracanaoperator delete
.C ++ pozwala dostroić obie te operacje: zarządzanie pamięcią oraz budowę / zniszczenie obiektu w przydzielonej pamięci. To ostatnie odbywa się poprzez pisanie konstruktorów i destruktorów dla klasy. Precyzyjne zarządzanie pamięcią odbywa się poprzez napisanie własnego
operator new
ioperator delete
.Pierwsza z podstawowych zasad przeciążania operatora - nie rób tego - dotyczy szczególnie przeciążenia
new
idelete
. Niemal jedynym powodem przeciążenia tych operatorów są problemy z wydajnością i ograniczenia pamięci , aw wielu przypadkach inne działania, takie jak zmiany w używanych algorytmach , zapewniają znacznie wyższy stosunek kosztów do zysków niż próba poprawienia zarządzania pamięcią.C ++ biblioteki standardowej jest wyposażony w zestaw predefiniowanych
new
idelete
operatorów. Najważniejsze z nich to:Pierwsze dwa przydzielają / zwalniają pamięć dla obiektu, a dwa ostatnie dla tablicy obiektów. Jeśli udostępnisz własne wersje, nie zostaną one przeciążone, ale zastąpią te ze standardowej biblioteki.
Jeśli przeładujesz
operator new
, zawsze powinieneś również przeładować dopasowanieoperator delete
, nawet jeśli nigdy nie zamierzasz do niego dzwonić. Powodem jest to, że jeśli konstruktor wyrzuci podczas oceny nowego wyrażenia, system wykonawczy zwróci pamięć dooperator delete
pasującej do tej,operator new
która została wywołana w celu przydzielenia pamięci do utworzenia obiektu. Jeśli nie podasz pasującegooperator delete
nazywana jest domyślna, co prawie zawsze jest błędne.W przypadku przeciążenia
new
idelete
należy również rozważyć przeciążenie wariantów macierzy.Umieszczenie
new
C ++ pozwala nowym operatorom i operatorom usuwać dodatkowe argumenty.
Tak zwane umieszczanie nowe pozwala utworzyć obiekt pod określonym adresem, który jest przekazywany do:
Standardowa biblioteka zawiera odpowiednie przeciążenia operatorów nowych i usuwających:
Zauważ, że w podanym powyżej przykładowym kodzie umieszczenia nowego
operator delete
nigdy nie jest wywoływany, chyba że konstruktor X zgłosi wyjątek.Możesz także przeciążać
new
idelete
innymi argumentami. Podobnie jak w przypadku dodatkowego argumentu dotyczącego umieszczenia nowego, argumenty te są również wymienione w nawiasach za słowem kluczowymnew
. Ze względów historycznych takie warianty są często nazywane umieszczaniem nowych, nawet jeśli ich argumenty nie dotyczą umieszczenia obiektu pod określonym adresem.Specyficzne dla klasy nowe i usuń
Najczęściej będziesz chciał dostroić zarządzanie pamięcią, ponieważ pomiar wykazał, że instancje określonej klasy lub grupy powiązanych klas są często tworzone i niszczone, a domyślne zarządzanie pamięcią systemu wykonawczego dostosowane do ogólna wydajność, zajmuje się nieefektywnie w tym konkretnym przypadku. Aby to poprawić, możesz przeciążyć nową i usunąć dla określonej klasy:
W ten sposób przeciążone, nowe i delete zachowują się jak statyczne funkcje składowe. Dla obiektów
my_class
Thestd::size_t
argumentem zawsze będziesizeof(my_class)
. Jednak operatory te są również wywoływane dla dynamicznie przydzielanych obiektów klas pochodnych , w którym to przypadku może być większy.Globalnie nowe i usuń
Aby przeciążyć globalną nowość i usunąć, wystarczy zastąpić predefiniowane operatory biblioteki standardowej naszą własną. Jednak rzadko trzeba to robić.
źródło
nothrow
nowych.Dlaczego
operator<<
funkcja przesyłania strumieniowego obiektów dostd::cout
lub do pliku nie może być funkcją składową ?Powiedzmy, że masz:
W związku z tym nie można używać:
Ponieważ
operator<<
funkcja przeciążenia jest przeciążonaFoo
, LHS operatora musi byćFoo
obiektem. Co oznacza, że będziesz musiał użyć:co jest bardzo nieintuicyjne.
Jeśli zdefiniujesz ją jako funkcję nie będącą członkiem,
Będziesz mógł używać:
co jest bardzo intuicyjne.
źródło