Standard C++

poprawność Const

co to jest “poprawność const”?

dobra rzecz. Oznacza to użycie słowa kluczowego const, aby zapobiec mutacji obiektów const.

na przykład, jeśli chcesz utworzyć funkcję f(), która zaakceptowała std::string, plus chcesz obiecać rozmówcom, że nie zmienią wywołującego std::string, który zostanie przekazany do f(), możesz f() otrzymać jej parametr std::string

  • void f1(const std::string& s); // Pass by reference-to-const
  • void f2(const std::string* sptr); // podaj wskaźnik-do-const
  • void f3(std::string s); // wartość Pass by

w przypadkach pass by reference-to – const I pass by pointer-to – const, wszelkie próby zmiany wartości wywołującegostd::string w ramach funkcji f() byłyby oznaczane przez kompilator jako błąd przy czas kompilacji. Ta kontrola jest wykonywana w całości w czasie kompilacji: nie ma miejsca na czas pracy ani kosztu prędkości dla const. W przypadku wartości pass by(f3()), wywołana funkcja otrzymuje kopię wywołującego std::string. Oznacza to, że f3() może zmienić swój localcopy, ale kopia jest niszczona, gdy f3() powraca. W szczególności f3() nie może zmienić obiektu std::stringwywołującego.

jako przeciwny przykład, załóżmy, że chcesz utworzyć funkcję g(), która zaakceptowała std::string, ale chcesz dać znać, że g() może zmienić obiekt std::string wywołującego. W tym przypadku możesz otrzymać g() swój parametrstd::string

  • void g1(std::string& s); // Pass by reference-to-non-const
  • void g2(std::string* sptr); // przekaż pointer-to-non-const

brak const w tych funkcjach mówi kompilatorowi, że mogą (ale nie muszą) zmieniać obiekt std::string. W ten sposób mogą przekazać swoją std::string do dowolnej funkcji f(), ale tylko f3()(ta, która otrzymuje swój parametr “przez wartość”) może przekazać swoją std::string do g1() lub g2(). Jeśli f1() lub f2()muszą wywołać funkcję g(), lokalna kopia obiektu std::string musi zostać przekazana do funkcji g(); parametr do f1() lub f2() nie może być bezpośrednio przekazany do funkcji g(). Np..,

void g1(std::string& s);void f1(const std::string& s){ g1(s); // Compile-time Error since s is const std::string localCopy = s; g1(localCopy); // Okay since localCopy is not const}

naturalnie w powyższym przypadku, wszelkie zmiany dokonane przez g1() są dokonywane w obiekcie localCopy, który jest lokalny dla f1(). w szczególności nie zostaną wprowadzone żadne zmiany w parametrze const, który został przekazany przez odniesienie do f1().

w jaki sposób “poprawność const” ma związek z bezpieczeństwem zwykłego typu?

deklarowanie const-ness parametru jest po prostu kolejną formą bezpieczeństwa typu.

jeśli okaże się, że zwykłe bezpieczeństwo typu pomaga w poprawności systemów (tak jest; zwłaszcza w dużych systemach), znajdzieszconst poprawność również pomaga.

zaletą poprawności const jest to, że uniemożliwia ona niezamierzone modyfikowanie czegoś, czego się nie spodziewałeś. Kończy się to koniecznością udekorowania kodu kilkoma dodatkowymi naciśnięciami klawiszy (słowo kluczowe const), z tym, że mówisz kompilatorowi i innym programistom dodatkowy kawałek ważnej informacji semantycznej— informacji, której kompilator używa, aby zapobiec błędom, a inni programiści używają jako dokumentacji.

koncepcyjnie można sobie wyobrazić, że na przykład const std::stringjest inną klasą niż zwykła std::string, ponieważ w wariancieconstkoncepcyjnie brakuje różnych operacji mutacyjnych dostępnych w wariancie innym niż const. Na przykład, możesz koncepcyjnie wyobrazić sobie, że const std::string po prostu nie ma operatora przypisania+= ani żadnych innych operacji mutacyjnych.

czy powinienem spróbować naprawić wszystko ” wcześniej “czy”później”?

na samym, bardzo, bardzo początku.

poprawność const powoduje efekt kuli śnieżnej: każdy const, kiedy dodasz “tutaj”, wymaga dodania “tam” o cztery więcej.”

Dodaj const wcześnie i często.

co oznacza “const X * p”?

oznacza to, że p wskazuje na obiekt klasy X, ale p nie może być użyte do zmiany tego obiektu X (oczywiście p może również być NULL).

Czytaj od prawej do lewej: “p jest wskaźnikiem do X, który jest stały .”

na przykład, jeśli Klasa X ma funkcję członka const, taką jak inspect() const, można powiedziećp->inspect(). Ale jeśli Klasa X ma funkcję członka innego niżconst o nazwie mutate(), jest to błąd, jeśli powiesz p->mutate().

co istotne, ten błąd jest wyłapywany przez kompilator w czasie kompilacji-nie są wykonywane testy w czasie wykonywania. Oznacza to, że constnie spowalnia Twojego programu i nie wymaga pisania dodatkowych przypadków testowych w celu sprawdzenia rzeczy w czasie wykonywania-kompilator wykonuje pracę w czasie kompilacji.

Jaka jest różnica między “const X * p”, “X* const p”i” const X* const p”?

odczytaj deklaracje wskaźnika od prawej do lewej.

  • const X* p oznacza ” p wskazuje na X czyli const“: obiektu Xnie można zmienić za pomocąp.
  • X* const p oznacza “p jest wskaźnikiem const na X, który nie jestconst“: nie możesz zmienić wskaźnika p, ale możesz zmienić obiekt Xpoprzez p.
  • const X* const p oznacza “p jest wskaźnikiem const na X czyli const“: nie można zmienić samego wskaźnika p, ani nie można zmienić obiektu X poprzez p.

i o tak, czy wspomniałem o przeczytaniu Twoich deklaracji wskaźnika od prawej do lewej?

co oznacza “const X & x”?

oznacza to, że x aliasy obiektu X, ale nie możesz zmienić tego obiektu X poprzez x.

Czytaj od prawej do lewej: “x jest odniesieniem do X czyli const.”

na przykład, jeśli Klasa X ma funkcję członka const, taką jak inspect() const, można powiedziećx.inspect(). Ale jeśli Klasa X ma funkcję członka innego niżconst o nazwie mutate(), jest to błąd, jeśli powiesz x.mutate().

jest to całkowicie symetryczne ze wskaźnikami do const, włączając w to fakt, że kompilator wykonuje wszystkie kontrole w czasie kompilacji, co oznacza, że const nie spowalnia Twojego programu i nie wymaga pisania dodatkowych przypadków testowych, aby sprawdzić rzeczy w czasie wykonywania.

co oznaczają “x const& x” i “X const* p”?

X const& xjest odpowiednikiem const X& x, a X const* x jest odpowiednikiem const X* x.

niektórzy wolą styl const-na-prawo, nazywając go “konsekwentny const” lub, używając terminu ukutego przez Simona Branda, “Wschód const.”Rzeczywiście styl” East const “może być bardziej spójny niż alternatywa: styl” East const ” zawsze obraca const po prawej stronie tego, co określa, podczas gdy inny styl czasami umieszcza const po lewej, a czasami po prawej (dla const deklaracji wskaźnika i const funkcji Członkowskich).

w stylu “East const” zmienna lokalna, która jest const, jest zdefiniowana za pomocąconstpo prawej stronie: int const a = 42;. Podobnie zmienna static, która jest constjest zdefiniowana jako static double const x = 3.14;.Zasadniczo każdy const kończy się po prawej stronie rzeczy, którą stanowi, w tym const, która jest wymagana do uzyskania prawej deklaracji wskaźnika const i funkcji członka const.

styl “East const” jest również mniej mylący, gdy jest używany z aliasami typów: dlaczego foo i bar mają tutaj różne typy?

using X_ptr = X*;const X_ptr foo;const X* bar;

użycie stylu “East const” sprawia, że jest to wyraźniejsze:

using X_ptr = X*;X_ptr const foo;X* const foobar;X const* bar;

wyraźnie widać, że foo i foobar to ten sam typ, A bar to inny typ.

styl “East const” jest również bardziej spójny z deklaracjami wskaźnika. Kontrast w tradycyjnym stylu:

const X** foo;const X* const* bar;const X* const* const baz;

w przypadku stylu “East const

X const** foo;X const* const* bar;X const* const* const baz;

pomimo tych korzyści, styl const -on-the-right nie jest jeszcze popularny, więc tradycyjny kod ma tendencję do tradycyjnego stylu.

czy “X& const x” ma jakiś sens?

nie, to nonsens.

aby dowiedzieć się, co oznacza powyższa deklaracja, przeczytaj ją od prawej do lewej: “x jest constodniesieniem do X“. Ale to jest zbędne — odniesienia są zawsze const, w tym sensie, że nigdy nie można ponownie umieścić odniesienia, aby odnieść się do innego obiektu. Nigdy. Z lub bez const.

innymi słowy, “X& const x” jest funkcjonalnie odpowiednikiem “X& x“. Ponieważ nic nie zyskujesz, dodając const po &, nie powinieneś go dodawać: to zmyli ludzi — const sprawi, że niektórzy ludzie pomyślą, że X to const, tak jakbyś powiedział “const X& x“.

co to jest “funkcja const member”?

funkcja składowa, która kontroluje (a nie mutuje) swój obiekt.

a const funkcja członu jest oznaczana przyrostkiem const tuż po liście parametrów funkcji członu. Funkcje Członkowskie z przyrostkiem const nazywane są “const funkcje Członkowskie” lub “inspektorzy.”Funkcje członkowskie bez przyrostkaconst nazywane są “funkcjami spozaconst” lub “mutatorami.”

class Fred {public: void inspect() const; // This member promises NOT to change *this void mutate(); // This member function might change *this};void userCode(Fred& changeable, const Fred& unchangeable){ changeable.inspect(); // Okay: doesn't change a changeable object changeable.mutate(); // Okay: changes a changeable object unchangeable.inspect(); // Okay: doesn't change an unchangeable object unchangeable.mutate(); // ERROR: attempt to change unchangeable object}

próba wywołania unchangeable.mutate() jest błędem wykrytym podczas kompilacji. Dla const nie ma miejsca w trybie runtime ani speedpenalty i nie musisz pisać przypadków testowych, aby sprawdzić je w trybie runtime.

końcowa const na inspect() funkcja member powinna być użyta, aby metoda nie zmieniła stanu obiektu abstract (Client-visible). To nieco różni się od stwierdzenia, że metoda nie zmieni “surowych bitów” obiektu struct. Kompilatory C++ nie mogą przyjmować interpretacji “bitowej”, chyba że są w stanie rozwiązać problem, który normalnie nie może zostać rozwiązany (np. może istnieć alias inny niżconst, który mógłby zmienić stan obiektu). Kolejny (ważny) wgląd w ten problem z aliasingiem: wskazywanie na obiekt wskaźnikiem-do – const nie gwarantuje, że obiekt się nie zmieni; jedynie obiecuje, że obiekt nie zmieni się za pomocą tego wskaźnika.

jaka jest zależność między funkcją return-by-reference a funkcją const?

jeśli chcesz zwrócić element obiektu this przez odniesienie z metody Inspektora, powinieneś zwrócić go za pomocą reference-to-const (const X& inspect() const) lub value (X inspect() const).

class Person {public: const std::string& name_good() const; // Right: the caller can't change the Person's name std::string& name_evil() const; // Wrong: the caller can change the Person's name int age() const; // Also right: the caller can't change the Person's age // ...};void myCode(const Person& p) // myCode() promises not to change the Person object...{ p.name_evil() = "Igor"; // But myCode() changed it anyway!!}

dobrą wiadomością jest to, że kompilator często cię złapie, jeśli się pomylisz. W szczególności, jeśli przypadkowo zwrócisz członka swojego obiektu this przez odniesienie inne niżconst, takie jak w Person::name_evil() powyżej, kompilator często go wykryje i spowoduje błąd podczas kompilacji wnętrz, w tym przypadku,Person::name_evil().

zła wiadomość jest taka, że kompilator nie zawsze cię złapie: są przypadki, w których kompilator po prostu nie zawsze wysyła Ci komunikat o błędzie w czasie kompilacji.

tłumaczenie: trzeba myśleć. Jeśli cię to przeraża, znajdź inną linię pracy; “myśl” nie jest czteroliterowym słowem.

pamiętaj o” const filozofii ” rozłożonej w tej sekcji: funkcja członka const nie musi zmieniać (lub zezwalać na zmianę wywołującego) stanu logicznego obiektu this (AKA stan abstrakcyjny AKA meaningwisestate). Pomyśl o tym, co oznacza obiekt, a nie jak jest on wewnętrznie realizowany. Wiek i imię osoby są logicznie części osoby, ale sąsiad osoby i pracodawca nie są. Metoda Inspektora, która zwraca część stanu logicznego / abstrakcyjnego / meaningwise obiektu thisnie może zwracać wskaźnika (lub referencji) innego niżconst do tej części,niezależnie od tego, czy ta część jest wewnętrznie zaimplementowana jako bezpośredni element danych fizycznie osadzony w obiekciethis, czy w inny sposób.

o co chodzi z “const-overloading”?

const przeciążenie pomaga osiągnąć const poprawność.

const przeciążenie jest wtedy, gdy masz metodę inspektora i metodę mutatora o tej samej nazwie i tej samej liczbie i typów parametrów. Dwie różne metody różnią się tylko tym, że theinspector jest const, a mutator nie jestconst.

najczęstszym zastosowaniem const przeciążenia jest operator kodu dolnego. Zazwyczaj powinieneś spróbować użyć jednego ze standardowych szablonów kontenerów, takich jak std::vector, ale jeśli chcesz stworzyć własną klasę, która ma operatora indeksu, oto zasada: operatory indeksu często występują w parach.

class Fred { /*...*/ };class MyFredList {public: const Fred& operator (unsigned index) const; // Subscript operators often come in pairs Fred& operator (unsigned index); // Subscript operators often come in pairs // ...};

Operator kodu dolnego const zwraca referencję const, więc kompilator zapobiegnie przypadkowemu zmienianiu/modyfikowaniu kodu Fred. Operator indeksu dolnego nieconst zwraca referencję nieconst, co jest sposobem, w jaki użytkownicy wywołujący (i kompilator) mogą modyfikować obiekt Fred.

gdy użytkownik twojej klasy MyFredListwywoła operatora skryptu dolnego, kompilator wybiera, które przeciążenie ma wywołać na podstawie stałości ich klasy MyFredList. Jeśli dzwoniący ma MyFredList a lub MyFredList& a, to a wywoła operatora podrzędnego innego niżconst , a dzwoniący skończy z odniesieniem innego niżconst do Fred:

na przykład załóżmy, że class Fred ma metodę Inspektora inspect() const i metodę mutatora mutate():

void f(MyFredList& a) // The MyFredList is non-const{ // Okay to call methods that inspect (look but not mutate/change) the Fred at a: Fred x = a; // Doesn't change to the Fred at a: merely makes a copy of that Fred a.inspect(); // Doesn't change to the Fred at a: inspect() const is an inspector-method // Okay to call methods that DO change the Fred at a: Fred y; a = y; // Changes the Fred at a a.mutate(); // Changes the Fred at a: mutate() is a mutator-method}

jednak jeśli dzwoniący ma const MyFredList a lub const MyFredList& a, to azadzwoni do const subscriptoperator, a dzwoniący skończy z const odniesieniem do Fred. Pozwala to na sprawdzenie Fredw a, ale zapobiega to przypadkowemu mutowaniu/zmianie Fred w a.

void f(const MyFredList& a) // The MyFredList is const{ // Okay to call methods that DON'T change the Fred at a: Fred x = a; a.inspect(); // Compile-time error (fortunately!) if you try to mutate/change the Fred at a: Fred y; a = y; // Fortunately(!) the compiler catches this error at compile-time a.mutate(); // Fortunately(!) the compiler catches this error at compile-time}

przeciążenie Konst dla operatorów indeksów dolnych i funcall jest zilustrowane TUTAJ,TUTAJ, TUTAJ, TUTAJ i tutaj.

możesz oczywiście również użyć const – overloading dla rzeczy innych niż operator kodu dolnego.

jak może mi pomóc zaprojektować lepsze klasy, jeśli odróżnię stan logiczny od stanu fizycznego?

ponieważ to zachęca do projektowania klas od zewnątrz, a nie od wewnątrz, co z kolei sprawia, że klasy i obiekty są łatwiejsze do zrozumienia i użycia, bardziej intuicyjne, mniej podatne na błędy i szybsze. (OK, to małe uproszczenie. Aby zrozumieć wszystkie “Jeśli” I “ale”, musisz po prostu przeczytać resztę tego tekstu!

zrozummy to od wewnątrz — Na Zewnątrz-będziesz (powinien) projektować swoje klasy od zewnątrz-w, ale jeśli jesteś nowy w tej koncepcji, łatwiej jest to zrozumieć od wewnątrz-Na Zewnątrz.

wewnątrz twoje obiekty mają stan fizyczny (lub konkretny lub bitowy). Jest to stan, który programiści mogą łatwo zobaczyć i zrozumieć; jest to stan, który byłby tam, gdyby klasa była tylko w stylu C struct.

Na Zewnątrz twoje obiekty mają użytkowników twojej klasy, A ci użytkownicy są ograniczeni do używania tylko public funkcji Członkowskich i friends. Ci zewnętrzni użytkownicy również postrzegają obiekt jako posiadający stan, na przykład, jeśli obiekt jest klasy Rectangle z metodami width(), height() i area(), Twoi użytkownicy powiedzieliby, że te trzy są częścią logicznego (lub abstrakcyjnego lub znaczącego) stanu obiektu. Dla użytkownika zewnętrznego obiekt Rectangle ma obszar, nawet jeśli obszar ten jest obliczany w locie (np. jeśli metoda area() zwróci iloczyn szerokości i wysokości obiektu). W rzeczywistości, i to jest ważny punkt, Twoi użytkownicy nie wiedzą i nie dbają o to, jak wdrożysz którąkolwiek z tych metod; użytkownicy nadal postrzegają, z ich perspektywy, że Twój obiekt ma logiczny stan szerokości, wysokości i powierzchni.

przykład area() pokazuje przypadek, w którym stan logiczny może zawierać elementy, które nie są bezpośrednio realizowane w stanie fizycznym. Jest również odwrotnie: klasy czasami celowo ukrywają część fizycznego (konkretnego, bitowego) stanu swoich obiektów przed użytkownikami-celowo nie dostarczają żadnych funkcjipubliclub friend, które pozwoliłyby użytkownikom czytać lub pisać, a nawet wiedzieć o tym ukrytym stanie. Oznacza to, że w stanie fizycznym obiektu nie ma odpowiednich elementów w stanie logicznym obiektu.

jako przykład tego drugiego przypadku, obiekt kolekcji może buforować swoje ostatnie wyszukiwanie w nadziei na poprawę wydajności następnego wyszukiwania. Ta pamięć podręczna jest z pewnością częścią stanu fizycznego obiektu, ale jest to detal wewnętrznej implementacji, który prawdopodobnie nie będzie wystawiony na działanie użytkowników-prawdopodobnie nie będzie częścią stanu logicznego obiektu. Mówienie, co jest łatwe, jeśli myślisz z zewnątrz-w: jeśli użytkownicy collection-object mają teraz możliwość sprawdzenia stanu samego bufora, to bufor jest przezroczysty i nie jest częścią stanu logicznego obiektu.

czy stałość moich funkcji Public member powinna być oparta na tym,co metoda robi ze stanem logicznym obiektu, czy fizycznym?

logiczne.

nie ma sposobu, aby ta następna część była łatwa. To będzie bolało. Najlepiej usiąść. I proszę, dla Twojego bezpieczeństwa, upewnij się, że w pobliżu nie ma ostrych narzędzi.

wróćmy do przykładu collection-object. Pamiętaj: istnieje metoda wyszukiwania, która pobiera ostatnie wyszukiwanie w nadziei na przyspieszenie przyszłych wyszukiwań.

powiedzmy to, co jest prawdopodobnie oczywiste: Załóżmy, że metoda lookup nie zmienia żadnego stanu logicznego obiektu Collection-object.

więc … nadszedł czas, aby cię zranić. Gotowy?

oto nadchodzi: jeśli metoda lookup nie wprowadza żadnych zmian w stanie logicznym obiektu collection-object, ale zmienia stan fizyczny obiektu collection-object (powoduje bardzo realną zmianę w bardzo realnym buforze), czy metoda lookup powinna być const?

odpowiedź brzmi zdecydowanie tak. (Istnieją wyjątki od każdej reguły, więc “tak” powinno mieć gwiazdkę obok,ale w zdecydowanej większości przypadków odpowiedź brzmi “tak”.)

tu chodzi o “logiczne const” nad “fizyczne const.”Oznacza to, że decyzja o tym, czy udekorować amethod const powinna zależeć przede wszystkim od tego, czy ta metoda pozostawi stan logiczny niezmieniony, niezależnie od tego(czy siedzisz?) (możesz chcieć usiąść) niezależnie od tego, czy metoda robi bardzo realchanges do bardzo rzeczywistego stanu fizycznego obiektu.

na wypadek, gdyby ci się nie udało, albo gdybyś jeszcze nie cierpiał, Podzielmy to na dwa przypadki:

  • jeśli metoda zmienia jakąkolwiek część stanu logicznego obiektu, logicznie jest mutatorem; nie powinna być const evenif (jak to się faktycznie dzieje!) metoda nie zmienia żadnych fizycznych bitów konkretnego stanu obiektu.
  • odwrotnie, metoda jest logicznie inspektorem i powinna być const, jeśli nigdy nie zmienia żadnej części stanu logicznego obiektu, nawet jeśli (tak się dzieje!) metoda zmienia fizyczne bity konkretnego stanu obiektu.

jeśli jesteś zdezorientowany, przeczytaj to jeszcze raz.

jeśli nie jesteś zdezorientowany, ale jesteś zły, dobrze: możesz tego jeszcze nie lubić, ale przynajmniej to rozumiesz. Weź głęboki oddech i powtórz za mną: “constness metody powinien mieć sens z zewnątrz obiektu.”

jeśli nadal jesteś zły, powtórz to trzy razy: “stałość metody musi mieć sens dla użytkowników obiektu, a ci użytkownicy mogą zobaczyć tylko stan logiczny obiektu.”

jeśli nadal jesteś zły, sorry, jest jak jest. Weź się w garść i żyj z tym. Tak, będą wyjątki; każda zasada jest ich. Ale z reguły, w głównej mierze, to logiczne const pojęcie jest dobre dla Ciebie i dobre dla Twojego oprogramowania.

jeszcze jedno. To będzie nieinane, ale bądźmy dokładni, czy metoda zmienia stan logiczny obiektu. Jeśli jesteś poza klasą-jesteś normalnym użytkownikiem, każdy eksperyment, który możesz wykonać (każda metoda lub Sekwencja metod, które wywołujesz) będzie miał takie same wyniki (te same wartości zwracane, te same wyjątki lub brak WYJĄTKÓW)niezależnie od tego, czy najpierw wywołałeś tę metodę wyszukiwania. Jeżeli funkcja lookup zmieniła jakiekolwiek przyszłe zachowanie każdej przyszłej metody (nie tylko przyspieszając jej działanie, ale zmieniając wynik, zmieniając wartość zwracaną, zmieniając exception), to metoda lookup zmieniła stan logiczny obiektu — jest to mutuator. Ale jeśli metoda lookupzmieniła nic innego, jak być może przyspieszyć niektóre rzeczy, to jest inspektorem.

co zrobić, jeśli chcę, aby funkcja członka const dokonała “niewidocznej” zmiany na członie danych?

użyj mutable (lub, w ostateczności, użyj const_cast).

niewielki odsetek inspektorów musi wprowadzić zmiany w stanie fizycznym obiektu, których nie mogą zaobserwować zewnętrzni użytkownicy — zmiany w stanie fizycznym, ale nie logicznym.

na przykład omawiany wcześniej obiekt collection-object buforował swoje ostatnie wyszukiwanie w nadziei poprawienia wydajności następnego wyszukiwania. Ponieważ cache, w tym przykładzie, nie może być bezpośrednio obserwowany przez żaden publiczny interfejs kolekcji-obiektu (inny niż czas), jego istnienie i stan nie są częścią stanu logicznego obiektu, więc zmiany w nim są niewidoczne dla zewnętrznych użytkowników. Metoda lookup jest inspektorem, ponieważ nigdy nie zmienia stanu logicznego obiektu, niezależnie od tego, że, przynajmniej dla obecnej implementacji, zmienia stan fizyczny obiektu.

gdy metody zmieniają stan fizyczny, ale nie logiczny, metoda powinna być ogólnie oznaczona jako const, ponieważ jest to metoda-Inspektor. Tworzy to problem: gdy kompilator zobaczy Twoją metodę const zmieniającą stan fizyczny obiektu this, będzie narzekał — wyświetli twojemu kodowi komunikat o błędzie.

język kompilatora C++ używa słowa kluczowego mutable, aby pomóc ci objąć to logiczne pojęcie const. W takim przypadku należy oznaczyć bufor słowem kluczowym mutable, dzięki czemu kompilator wie, że można go zmienić wewnątrz metodyconst lub za pomocą dowolnego innego wskaźnika lub referencji const. W naszym żargonie słowo kluczowe mutable oznacza te porcje stanu fizycznego obiektu, które nie są częścią stanu logicznego.

słowo kluczowe mutableznajduje się tuż przed deklaracją członka danych, czyli w tym samym miejscu, w którym można umieścićconst. Innym podejściem, nie preferowanym, jest odrzucenie const‘ness wskaźnika this, prawdopodobnie za pomocą słowa kluczowegoconst_cast :

Set* self = const_cast<Set*>(this); // See the NOTE below before doing this!

po tej linii, self będzie miał te same bity co this, to znaczy self == this, ale self jest Set* zamiastconst Set* (technicznie this jest const Set* const, ale prawo-większość const nie ma znaczenia dla tej dyskusji).Oznacza to, że możesz użyć self, aby zmodyfikować obiekt wskazywany przez this.

Uwaga: Istnieje bardzo mało prawdopodobny błąd, który może wystąpić w przypadku const_cast. Dzieje się tak tylko wtedy, gdy trzy bardzo rzadkie przypadki są połączone w tym samym czasie: element danych, który powinien być mutable (taki jak omówiono powyżej), kompilator, który nie obsługuje słowa kluczowego mutable i/lub programista, który go nie używa, oraz obiekt, który został pierwotnie zdefiniowany jako const (w przeciwieństwie do normalnego, Nie-const obiektu, na który wskazuje wskaźnik-do-const).Chociaż ta kombinacja jest tak rzadka, że może nigdy ci się nie przydarzyć, jeśli kiedykolwiek się zdarzy, kod może nie działać (standard mówi, że zachowanie jest niezdefiniowane).

jeśli kiedykolwiek chcesz użyć const_cast, użyj mutable zamiast tego. Innymi słowy, jeśli kiedykolwiek trzeba zmienić element anobject, a Obiekt ten jest wskazywany przez wskaźnik-do – const, najbezpieczniejszą i najprostszą rzeczą do zrobienia jest dodanie mutable do deklaracji członka. Możesz użyć const_cast, jeśli jesteś pewien, że obiekt nie jest const (np. jeśli jesteś pewien, że obiekt jest zadeklarowany tak: Set s;), ale jeśli sam obiekt może być const (np. ifit może być zadeklarowany tak: const Set s;), użyj mutable zamiast const_cast.

proszę nie pisać, że wersja X kompilatora Y na maszynie z Pozwala na zmianę elementu nie-mutable obiektuconst. Nie obchodzi mnie to-jest to nielegalne w zależności od języka i twój kod prawdopodobnie zawiedzie na innym kompilatorze lub nawet innej wersji (upgrade) tego samego kompilatora. Po prostu powiedz nie. Zamiast tego użyj mutable. Napisz kod, który gwarantuje działanie, a nie kod, który nie wydaje się łamać.

czy const_cast oznacza utracone możliwości optymalizacji?

w teorii tak, w praktyce nie.

nawet jeśli język zakazał const_cast, jedynym sposobem uniknięcia przepłukiwania pamięci podręcznej rejestru w wywołaniu memberfunction const byłoby rozwiązanie problemu aliasingu (tj., aby udowodnić, że nie ma wskaźników innych niżconst, które wskazywałyby na obiekt). Może się to zdarzyć tylko w rzadkich przypadkach (gdy obiekt jest skonstruowany w zakresie wywołania funkcji const i gdy wszystkie wywołania funkcji nieconst między konstrukcją obiektu a wywołaniem funkcjiconst są statycznie związane, i gdy każde z tych wywołań jest również inlined, a sam konstruktor jest inlined, A gdy dowolne funkcje członowe wywołane przez konstruktora są inline).

dlaczego kompilator pozwala mi zmienić int po wskazaniu na niego const int*?

bo “const int* p “oznacza” p obiecuje nie zmieniać *p, “nie” *p obiecuje nie zmieniać.”

powoduje, że const int* wskazuje na int nie const-jeśli int. int nie można zmienić za pomocąconst int*, ale jeśli ktoś inny ma int* (uwaga: nie const), który wskazuje na (“aliasy”) to samo int, toint*może być użyty do zmiany int. Na przykład:

void f(const int* p1, int* p2){ int i = *p1; // Get the (original) value of *p1 *p2 = 7; // If p1 == p2, this will also change *p1 int j = *p1; // Get the (possibly new) value of *p1 if (i != j) { std::cout << "*p1 changed, but it didn't change via pointer p1!\n"; assert(p1 == p2); // This is the only way *p1 could be different }}int main(){ int x = 5; f(&x, &x); // This is perfectly legal (and even moral!) // ...}

zauważ, że main() i f(const int*,int*) mogą być w różnych jednostkach kompilacji, które są kompilowane w różnych dniach tygodnia. W takim przypadku kompilator nie jest w stanie wykryć aliasingu podczas kompilacji. Dlatego nie ma sposobu, abyśmy mogli stworzyć regułę językową, która zabraniałaby tego rodzaju rzeczy. W rzeczywistości nie chcielibyśmy nawet tworzyć takiego arule, ponieważ ogólnie jest to funkcja, która może mieć wiele wskaźników wskazujących na to samo. Fakt, że jeden z tych wskaźników obiecuje, że nie zmieni podstawowej “rzeczy”, jest tylko obietnicą złożoną przez wskaźnik; to nie jest obietnica złożona przez “rzecz”.

czy “const * P” oznacza, że * p nie może się zmienić?

Nie! (Jest to związane z FAQ dotyczącym aliasowania wskaźników int.)

const Fred* p” oznacza, że Fred nie można zmienić za pomocą wskaźnika p, ale mogą istnieć inne sposoby dotarcia do obiektu bez przechodzenia przez const (takie jak aliasowany wskaźnik inny niżconst, taki jak Fred*). Na przykład, jeśli masz dwa wskaźniki “const Fred* p” i “Fred* q“, które wskazują na ten sam obiekt Fred (aliasing), wskaźnik q może być użyty do zmiany obiektu Fred, ale wskaźnik p nie może.

class Fred {public: void inspect() const; // A const member function void mutate(); // A non-const member function};int main(){ Fred f; const Fred* p = &f; Fred* q = &f; p->inspect(); // Okay: No change to *p p->mutate(); // Error: Can't change *p via p q->inspect(); // Okay: q is allowed to inspect the object q->mutate(); // Okay: q is allowed to mutate the object f.inspect(); // Okay: f is allowed to inspect the object f.mutate(); // Okay: f is allowed to mutate the object // ...}

dlaczego otrzymuję błąd konwersji Foo** → const Foo**?

ponieważ konwersja Foo**const Foo** byłaby Nieprawidłowa i niebezpieczna.

C++ pozwala na (bezpieczną) konwersję Foo*Foo const*, ale daje błąd, jeśli próbujesz pośrednio przekonwertować Foo**const Foo**.

uzasadnienie tego błędu jest podane poniżej. Ale najpierw jest najczęstsze rozwiązanie: simplychange const Foo** do const Foo* const*:

class Foo { /* ... */ };void f(const Foo** p);void g(const Foo* const* p);int main(){ Foo** p = /*...*/; // ... f(p); // ERROR: it's illegal and immoral to convert Foo** to const Foo** g(p); // Okay: it's legal and moral to convert Foo** to const Foo* const* // ...}

powodem, dla którego konwersja z Foo**const Foo** jest niebezpieczna, jest to, że pozwoli Ci cicho i przypadkowo zmodyfikować const Foo obiekt bez obsady:

class Foo {public: void modify(); // make some modification to the this object};int main(){ const Foo x; Foo* p; const Foo** q = &p; // q now points to p; this is (fortunately!) an error *q = &x; // p now points to x p->modify(); // Ouch: modifies a const Foo!! // ...}

jeśli linia q = &p byłaby legalna, qwskazywałaby na p. Następna linia, *q = &x, zmienia samą p (ponieważ *qto p) na x. To byłoby złe, ponieważ przegraliśmy kwalifikator const: p to Foo*, alexto const Foo. Linia p->modify() wykorzystuje zdolność pdo modyfikowania swojego referenta, co jest prawdziwym problemem,ponieważ skończyło się na modyfikacji const Foo.

w drodze analogii, jeśli ukryjesz przestępcę pod legalnym przebraniem, może on wykorzystać zaufanie, jakim obdarzył to przebranie.To źle.

na szczęście C++ uniemożliwia ci to: linia q = &p jest oznaczana przez kompilator C++ jako compile-timeerror. Przypomnienie: proszę nie wskazywać-cast swój sposób wokół tego komunikatu o błędzie kompilacji. Po Prostu Powiedz Nie!

(Uwaga: Istnieje koncepcyjne podobieństwo między tym a zakazem konwersji Derived** naBase**.)

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany.