Sprzedaj mi na stałej poprawności

135

Dlaczego więc właściwie zawsze zaleca się używanie const tak często, jak to możliwe? Wydaje mi się, że używanie const może być bardziej uciążliwe niż pomoc w C ++. Ale z drugiej strony, podchodzę do tego z perspektywy Pythona: jeśli nie chcesz, aby coś zostało zmienione, nie zmieniaj tego. W związku z tym, oto kilka pytań:

  1. Wygląda na to, że za każdym razem, gdy oznaczam coś jako const, pojawia się błąd i muszę gdzieś zmienić inną funkcję, aby też była stała. To powoduje, że muszę zmienić inną funkcję w innym miejscu. Czy to jest coś, co staje się łatwiejsze wraz z doświadczeniem?

  2. Czy korzyści z używania const naprawdę wystarczają, aby zrekompensować kłopoty? Jeśli nie zamierzasz zmieniać obiektu, dlaczego nie napisać kodu, który go nie zmienia?

Powinienem zauważyć, że w tym momencie najbardziej koncentruję się na korzyściach wynikających z używania const do celów poprawności i łatwości konserwacji, chociaż miło jest również mieć pojęcie o implikacjach wydajnościowych.

Jason Baker
źródło
1
8-letnia retrospektywa, ale… Co powiesz na 100-krotne przyspieszenie kodu (widziałem to na żywo)?
lorro
1
Sprawdź moją odpowiedź tutaj (pytanie powiązane, powiedziałbym, że ta sama odpowiedź): stackoverflow.com/questions/42310477/… Przy okazji: kochamconst
Gines Hidalgo

Odpowiedzi:

160

To jest ostateczny artykuł o „poprawności const”: https://isocpp.org/wiki/faq/const-correctness .

Krótko mówiąc, używanie const jest dobrą praktyką, ponieważ ...

  1. Chroni przed przypadkową zmianą zmiennych, które nie są przeznaczone do zmiany,
  2. Chroni Cię przed przypadkowymi przypisaniami zmiennych i
  3. Kompilator może go zoptymalizować. Na przykład jesteś chroniony przed

    if( x = y ) // whoops, meant if( x == y )
    

Jednocześnie kompilator może generować bardziej wydajny kod, ponieważ przez cały czas dokładnie wie, jaki będzie stan zmiennej / funkcji. Jeśli piszesz zwarty kod C ++, to dobrze.

Masz rację, ponieważ konsekwentne używanie stałej poprawności może być trudne, ale kod końcowy jest bardziej zwięzły i bezpieczniejszy w programowaniu. Kiedy dużo pracujesz nad C ++, korzyści z tego szybko się ujawniają.

Jordan Parmer
źródło
Zawsze zakładałem, że wartości const można swobodnie buforować, a tym samym uniknąć wielu problemów z współbieżnością lub poprawić szybkość. Czy to prawda?
Phil H
3
Pewnie. constzmienne mogą zwiększyć szybkość w tym sensie, że kompilator może wygenerować bardziej optymalny kod, ponieważ zna pełny cel i użycie zmiennej. Czy zauważysz różnicę? Cóż, jest to dyskusyjne w przypadku twojego wniosku. Jeśli chodzi o współbieżność, zmienne const są tylko do odczytu, co oznacza, że ​​nie ma potrzeby blokowania tych zmiennych na wyłączność, ponieważ wartość zawsze będzie taka sama.
Jordan Parmer
4
Począwszy od C ++ 11, biblioteka standardowa również zakłada, constże jest bezpieczna dla wątków, a traktowanie własnego kodu w ten sposób ułatwia sprawdzenie możliwych problemów wielowątkowych i jest łatwym sposobem na zapewnienie gwarancji API dla użytkowników API.
pmr
3
Dla mnie jedną z największych wartości dodanych poprawności const jest to, że wiesz po prostu patrząc na prototypy funkcji, kiedy wskaźniki odwołują się do danych, które nigdy nie są modyfikowane przez funkcję (tj. Tylko dane wejściowe).
cp.engr
1
Nie zapominaj, że istnieje logiczna i fizyczna stabilność. Każdy zawarty obiekt zagregowany, który jest oznaczony jako zmienny, może ulec zmianie, chociaż oznaczenie go jako takiego oznacza, że ​​nie ma on nic wspólnego ze stałością logiczną obiektu zawierającego.
Adrian
129

Oto fragment kodu z typowym błędem, przed którym może Cię chronić poprawność const:

void foo(const int DEFCON)
{
   if (DEFCON = 1)     //< FLAGGED AS COMPILER ERROR! WORLD SAVED!
   {
       fire_missiles();
   }
}
Doug T.
źródło
19
Więc mówimy, że const jest konieczne, ponieważ jakiś cholerny idiota wybrał "=" jako operator przypisania w C? ;-)
Steve Jessop
37
Oczywiście większość współczesnych kompilatorów ostrzega o przypisaniach warunkowych i wszyscy włączamy flagę traktuj ostrzeżenia jako błędy, prawda: ->
Jack Bolding
7
Nie bądź zbyt surowy na tym przykładzie: jest to ten sam przykład ewangelizowany przez niektórych programistów, aby przekonać nas do użycia if (0 == i) zamiast if (i == 0). Wreszcie wzorzec kodu Douga może być użyty poza instrukcją „if”. Co ważne, w humorystyczny sposób pokazuje jedną z zalet const. + 1.
paercebal,
2
Niektóre kompilatory (i lints) ostrzegały przed tym od dawna i jest to znacznie bardziej przydatne do wychwycenia tej literówki niż const: codepad.org/Nnf5JUXV .
7
@Roger zakładasz, że żyjemy w świecie, w którym kompilacje są czyste i wolne od ostrzeżeń. Z mojego doświadczenia wynika, że ​​w wielu kodach ostrzeżenia giną w szumie. Istnieje również wiele kodów, które przypisują w wyrażeniu if i wielu będzie argumentować, że jest to poprawny, „dobry” styl.
Doug T.
63

Wygląda na to, że za każdym razem, gdy oznaczam coś jako const, pojawia się błąd i muszę gdzieś zmienić inną funkcję, aby też była stała. To powoduje, że muszę zmienić inną funkcję w innym miejscu. Czy to jest coś, co staje się łatwiejsze wraz z doświadczeniem?

Z doświadczenia wynika, że ​​to totalny mit. Dzieje się tak, gdy niepoprawny jest stały kod z poprawnym stałym kodem, jasne. Jeśli projektujesz const-correct od samego początku, NIGDY nie powinno to stanowić problemu. Jeśli się coś const, a potem coś innego nie complile, kompilator mówi ci coś niezwykle ważne i należy poświęcić trochę czasu, aby to naprawić poprawnie .

Chris Mayer
źródło
7
Pomogło mi to zapobiec tak wielu błędom, w których miałem funkcję const, która miała wywołać funkcję inną niż const, ponieważ zapomniałem, że modyfikuje dane pod spodem.
Mooing Duck,
11
Bardzo niewiele osób zawsze zaczyna od czystego kodu. Większość kodowania polega na utrzymaniu starszego kodu, gdzie cytowany komentarz jest dość dokładny. Używam const podczas pisania nowych funkcji liści, ale zastanawiam się, czy warto gonić za czymś przez pół tuzina lub kilkanaście poziomów wywołań nieznanego kodu.
Warren Dew
3
Z mojego doświadczenia wynika, że ​​jest to całkowicie błędne. Zagnieżdżone typy punktowe powodują największy ból głowy w tego rodzaju sytuacjach, w których można mieć wiele kwantyfikatorów stałych w jednym typie.
Noldorin
4
„Jeśli projektujesz stałą poprawność od samego początku”, ile razy faktycznie masz luksus wymuszania stałej poprawności od samego początku? Większość z nas codziennie ma do czynienia z licznymi interfejsami API i bibliotekami, w których tak nie jest i niewiele można z tym zrobić. Tak zgadzam się z sentymentem ops „Wydaje się, że za każdym razem, Mark I coś jak const otrzymuję komunikat o błędzie na” ...
nyholku
@WarrenDew To jest argument przechodni; gdyby pierwotni autorzy użyli constprawidłowo (tj. „od początku”), to rzeczywiście nie byłoby problemu, o co dokładnie chodzi!
Wyścigi lekkości na orbicie
31

To nie jest dla Ciebie, kiedy piszesz kod na początku. Jest dla kogoś innego (lub dla Ciebie kilka miesięcy później), który patrzy na deklarację metody wewnątrz klasy lub interfejsu, aby zobaczyć, co robi. Brak modyfikowania obiektu jest istotną informacją, którą można z tego wyciągnąć.

Antonio Haley
źródło
1
To tylko trochę prawda. Const jest również używany jako ochrona do wymuszania zmiennych wewnętrznych za pośrednictwem interfejsów i tak dalej.
Jordan Parmer,
Tak, ale możesz to egzekwować poprzez zdyscyplinowane praktyki kodowania i stany na plakacie. Prawdziwa korzyść wynika z tego, co zostało odzwierciedlone w API.
Antonio Haley,
2
Dokładnie. Lepiej mieć „const int Value = 5;” niż mieć „int ConstValue = 5;”. +1.
paercebal,
6
Właściwy czas na wprowadzenie stałej poprawności jest wtedy, gdy początkowo piszesz API. W przeciwnym razie będziesz mieć kłopoty z zatruciem const podczas modernizacji (dlatego dodanie go do starego kodu jest zupełnie inną sprawą niż zrobienie tego w nowym kodzie).
Donal Fellows
@DonalFellows to nie mówi, że wstawiamy później const. Mówi się, że korzyści odniesiesz później, kiedy przeczytasz kod z już obecną const
Caleth
27

Jeśli używasz const rygorystycznie, zdziwiłbyś się, jak niewiele rzeczywistych zmiennych występuje w większości funkcji. Często nie więcej niż licznik pętli. Jeśli twój kod osiąga ten punkt, wewnątrz czujesz się ciepło ... sprawdzanie poprawności przez kompilację ... królestwo programowania funkcjonalnego jest blisko ... możesz go teraz prawie dotknąć ...

QBziZ
źródło
1
od czasu C ++ 17 i dobroci constexpr piszę testy jednostkowe czasu kompilacji ... coraz bliżej ...
QBziZ
22

const to obietnica, którą składasz jako programista i pozyskuje pomoc kompilatora w egzekwowaniu.

Moje powody, dla których mam stałą poprawność:

  • Informuje klientów Twojej funkcji, że nie zmienisz zmiennej ani obiektu
  • Akceptowanie argumentów przez odwołanie do stałej daje efektywność przekazywania przez referencję z bezpieczeństwem przekazywania wartości.
  • Zapisanie interfejsów jako poprawnych const umożliwi klientom korzystanie z nich. Jeśli napiszesz swój interfejs tak, aby przyjmował referencje inne niż const, klienci używający const będą musieli odrzucić constness, aby pracować z tobą. Jest to szczególnie denerwujące, jeśli twój interfejs akceptuje znaki * inne niż const, a twoi klienci używają std :: strings, ponieważ możesz uzyskać z nich tylko const char *.
  • Użycie const zachęci kompilator do zachowania uczciwości, więc nie pomylisz się z czymś, co nie powinno się zmienić.
JohnMcG
źródło
20

Moja filozofia jest taka, że ​​jeśli masz zamiar używać wymagającego języka z kontrolą czasu kompilacji, to najlepiej go wykorzystaj. constto wymuszony przez kompilator sposób komunikowania tego, co masz na myśli ... jest lepszy niż komentarze lub doxygen. Płacisz cenę, dlaczego nie wyliczyć wartości?

Pat Notz
źródło
20

Programowanie C ++ bez const jest jak jazda bez zapiętych pasów bezpieczeństwa.

Zapinanie pasa bezpieczeństwa za każdym razem, gdy wchodzisz do samochodu, jest uciążliwe, a 364 z 365 dni bezpiecznie dotrzesz do celu.

Jedyną różnicą jest to, że kiedy będziesz miał kłopoty z samochodem, poczujesz to od razu, podczas gdy przy programowaniu bez const być może będziesz musiał szukać przez dwa tygodnie, co spowodowało tę awarię, tylko po to, aby dowiedzieć się, że nieumyślnie pomieszałeś argument funkcji, który przeszedłeś przez odniesienie non-const dla wydajności.

andreas buykx
źródło
18

W przypadku programowania wbudowanego constrozsądne używanie podczas deklarowania globalnych struktur danych może zaoszczędzić dużo pamięci RAM, powodując umieszczanie stałych danych w pamięci ROM lub pamięci flash bez kopiowania do pamięci RAM podczas rozruchu.

W codziennym programowaniu constostrożne używanie pomaga uniknąć pisania programów, które ulegają awariom lub zachowują się nieprzewidywalnie, ponieważ próbują modyfikować literały ciągów i inne stałe dane globalne.

Podczas pracy z innymi programistami nad dużymi projektami, constprawidłowe użycie pomaga zapobiegać dławieniu przez innych programistów.

bk1e
źródło
13

constpomaga wyodrębnić kod, który „zmienia rzeczy” za Twoimi plecami. Tak więc w klasie oznaczasz wszystkie metody, które nie zmieniają stanu obiektu, jako const. Oznacza to, że constinstancje tej klasy nie będą już mogły wywoływać żadnych constmetod niebędących metodami. W ten sposób zapobiega się przypadkowemu wywołaniu funkcji, która może zmienić Twój obiekt.

Jest również constczęścią mechanizmu przeciążenia, więc możesz mieć dwie metody z identycznymi podpisami, ale jedną z consti jedną bez. Ten, który constma constodniesienia, jest wezwany do odwołań, a drugi do braku constodniesień.

Przykład:

#include <iostream>

class HelloWorld {
    bool hw_called;

public:
    HelloWorld() : hw_called(false) {}

    void hw() const {
        std::cout << "Hello, world! (const)\n";
        // hw_called = true;  <-- not allowed
    }

    void hw() {
        std::cout << "Hello, world! (non-const)\n";
        hw_called = true;
    }
};

int
main()
{
    HelloWorld hw;
    HelloWorld* phw1(&hw);
    HelloWorld const* phw2(&hw);

    hw.hw();    // calls non-const version
    phw1->hw(); // calls non-const version
    phw2->hw(); // calls const version
    return 0;
}
Chris Jester-Young
źródło
13

Poprawność const jest jedną z tych rzeczy, które naprawdę muszą być wprowadzone od samego początku. Jak już zauważyłeś, dodanie go później jest dużym problemem, zwłaszcza gdy istnieje duża zależność między nowymi funkcjami, które dodajesz, a starymi funkcjami niepoprawnymi do stałej, które już istnieją.

W przypadku dużej części kodu, który piszę, było to naprawdę warte wysiłku, ponieważ często używamy kompozycji:

class A { ... }
class B { A m_a; const A& getA() const { return m_a; } };

Gdybyśmy nie mieli stałej poprawności, musiałbyś uciekać się do zwracania złożonych obiektów według wartości, aby upewnić się, że nikt nie manipuluje stanem wewnętrznym klasy B za twoimi plecami.

Krótko mówiąc, stała poprawność jest obronnym mechanizmem programowania, który uchroni Cię przed bólem w przyszłości.

Peter Kovacs
źródło
7

Powiedzmy, że masz zmienną w Pythonie. Wiesz, że nie powinieneś go modyfikować. A jeśli przypadkowo to zrobisz?

C ++ daje ci sposób na zabezpieczenie się przed przypadkowym zrobieniem czegoś, czego nie powinieneś być w stanie zrobić w pierwszej kolejności. Z technicznego punktu widzenia i tak można to ominąć, ale musisz włożyć dodatkową pracę, aby się zastrzelić.

Greg Rogers
źródło
4

Jest ładny artykuł tutaj o const w C ++. To dość prosta opinia, ale mam nadzieję, że niektórym pomoże.

Ólafur Waage
źródło
3

Używając słowa kluczowego „const”, określasz inny interfejs dla swoich klas. Istnieje interfejs, który zawiera wszystkie metody i interfejs, który zawiera tylko metody const. Oczywiście pozwala to ograniczyć dostęp do niektórych rzeczy, których nie chcesz zmieniać.

Tak, z czasem staje się to łatwiejsze.

Wesley Tarle
źródło
2

Lubię stałą poprawność ... w teorii. Za każdym razem, gdy próbowałem zastosować go rygorystycznie w praktyce, w końcu się zepsuł i const_cast zaczyna pełzać, czyniąc kod brzydkim.

Może to tylko wzorce projektowe, których używam, ale const zawsze kończy się zbyt szerokim pędzlem.

Na przykład, wyobraź sobie prosty silnik bazy danych ... ma obiekty schematu, tabele, pola itp. Użytkownik może mieć wskaźnik 'const Table', co oznacza, że ​​nie może modyfikować samego schematu tabeli ... ale co z manipulowaniem dane powiązane z tabelą? Jeśli metoda Insert () jest oznaczona jako const, wówczas wewnętrznie musi odrzucić tę stałą, aby faktycznie manipulować bazą danych. Jeśli nie jest oznaczony jako const, nie chroni przed wywołaniem metody AddField.

Być może odpowiedzią jest podzielenie klasy na podstawie wymagań dotyczących ciągłości, ale to zwykle komplikuje projekt bardziej, niż bym chciał ze względu na korzyści, jakie przynosi.

Rob Walker
źródło
Myślę, że twój przykład to przypadek nadużywania const, ale nie zapomnij o modyfikatorze mutable, który można zastosować do zmiennych instancji, aby usunąć stałą.
Zooba
2
Kiedy funkcja Insert () byłaby kiedykolwiek oznaczana jako const, chyba że jej nazwa została niewłaściwie? Dodanie rzeczy zwykle modyfikuje rzecz, do której ją dodałeś, co oznacza, że ​​nie jest stała. To, czego naprawdę chciałeś, to TableWithConstSchema.
Greg Rogers,
Mógłbym dodać inną klasę, ale nie chcę, aby API było niepotrzebnie skomplikowane wyłącznie ze względu na stałą poprawność.
Rob Walker,
1

Możesz również podać wskazówki kompilatora za pomocą const ... zgodnie z poniższym kodem

#include <string>

void f(const std::string& s)
{

}
void x( std::string& x)
{
}
void main()
{
    f("blah");
    x("blah");   // won't compile...
}
Keith Nicholas
źródło