14.4 — Obiekty klas Const i funkcje składowe const

W lekcji >5.1 -- Zmienne stałe (zwane stałymi), dowiedziałeś się, że obiekty podstawowego typu danych (int, double, char itd.) można uczynić stałymi za pomocą słowa kluczowego const . Wszystkie zmienne const muszą zostać zainicjowane w momencie tworzenia.

const int x;      // compile error: not initialized
const int y{};    // ok: value initialized
const int z{ 5 }; // ok: list initialized

Podobnie obiekty typu klasowego (struktura, klasy i związki) mogą również zostać ustawione na stałe za pomocą słowa kluczowego const . Takie obiekty muszą być również inicjalizowane w momencie tworzenia.

struct Date
{
    int year {};
    int month {};
    int day {};
};

int main()
{
    const Date today { 2020, 10, 14 }; // const class type object

    return 0;
}

Podobnie jak w przypadku normalnych zmiennych, zazwyczaj chcesz, aby obiekty typu klasy były const (lub constexpr), gdy chcesz mieć pewność, że nie zostaną zmodyfikowane po utworzeniu.

Modyfikowanie elementów danych obiektów const jest niedozwolone

Po zainicjowaniu obiektu klasy const jakakolwiek próba modyfikacji elementów danych obiektu jest niemożliwa niedozwolone, gdyż naruszałoby to stałość obiektu. Obejmuje to zarówno bezpośrednią zmianę zmiennych składowych (jeśli są publiczne), jak i wywoływanie funkcji składowych, które ustawiają wartość zmiennych składowych.

struct Date
{
    int year {};
    int month {};
    int day {};

    void incrementDay()
    {
        ++day;
    }
};

int main()
{
    const Date today { 2020, 10, 14 }; // const

    today.day += 1;        // compile error: can't modify member of const object
    today.incrementDay();  // compile error: can't call member function that modifies member of const object

    return 0;
}

Obiekt Const nie może wywoływać funkcji składowych innych niż const

Możesz być zaskoczony, gdy zauważysz, że ten kod również powoduje błąd kompilacji:

#include <iostream>

struct Date
{
    int year {};
    int month {};
    int day {};

    void print()
    {
        std::cout << year << '/' << month << '/' << day;
    }
};

int main()
{
    const Date today { 2020, 10, 14 }; // const

    today.print();  // compile error: can't call non-const member function

    return 0;
}

Mimo że print() nie próbuje modyfikować zmiennej składowej, nasze wywołanie today.print() jest nadal stałe naruszenie. Dzieje się tak, ponieważ sama print() funkcja składowa nie jest zadeklarowana jako const. Kompilator nie pozwoli nam wywołać funkcji składowej innej niż stała w obiekcie const.

Funkcje składowe Const

Aby rozwiązać powyższy problem, musimy utworzyć print() funkcję składową const. A funkcja składowa const jest funkcją składową, która gwarantuje, że nie zmodyfikuje obiektu ani nie wywoła żadnych funkcji składowych innych niż stałe (ponieważ mogą modyfikować obiekt).

Utworzenie print() funkcji składowej const jest łatwe — po prostu dołączamy const słowo kluczowe do prototypu funkcji, po liście parametrów, ale przed treścią funkcji:

#include <iostream>

struct Date
{
    int year {};
    int month {};
    int day {};

    void print() const // now a const member function
    {
        std::cout << year << '/' << month << '/' << day;
    }
};

int main()
{
    const Date today { 2020, 10, 14 }; // const

    today.print();  // ok: const object can call const member function

    return 0;
}

W powyższym przykładzie print() została wykonana stała funkcja składowa, co oznacza, że możemy ją wywołać na obiektach stałych (takich jak today).

Dla zaawansowanych czytelników

W przypadku funkcji składowych zdefiniowanych poza definicją klasy, słowo kluczowe const musi zostać użyte zarówno w deklaracji funkcji w definicji klasy, jak i w definicji funkcji poza definicją klasy. Przykład tego pokazujemy w lekcji 15.2 — Klasy i pliki nagłówkowe.

Konstruktory nie mogą być stałe, ponieważ muszą zainicjować elementy obiektu, co wymaga ich modyfikacji. Konstruktory omówimy w lekcji 14.9 -- Wprowadzenie do konstruktorów.

Funkcja składowa const, która próbuje zmienić element członkowski danych lub wywołać funkcję składową inną niż stała, spowoduje wystąpienie błędu kompilatora. Na przykład:

struct Date
{
    int year {};
    int month {};
    int day {};

    void incrementDay() const // made const
    {
        ++day; // compile error: const function can't modify member
    }
};

int main()
{
    const Date today { 2020, 10, 14 }; // const

    today.incrementDay();

    return 0;
}

W tym przykładzie incrementDay() została oznaczona jako funkcja składowa stała, ale tak się nie stanie. próbuje zmienić day. Spowoduje to błąd kompilatora.

Funkcje członkowskie const mogą modyfikować elementy niebędące członkami (takie jak zmienne lokalne i parametry funkcji) i wywoływać funkcje niebędące członkami w zwykły sposób. Stała ma zastosowanie tylko do elementów członkowskich.

Kluczowa informacja

Funkcja składowa const nie może: modyfikować obiektu ukrytego, wywoływać funkcji składowych niebędących stałymi.
Funkcja składowa const może: modyfikować obiekty które nie są obiektem ukrytym, wywołaj funkcje składowe const, wywołaj funkcje niebędące członkami.

Funkcje członkowskie Const mogą być wywoływane na obiektach innych niż const

Funkcje członkowskie Const mogą być również wywoływane na obiektach innych niż const:

#include <iostream>

struct Date
{
    int year {};
    int month {};
    int day {};

    void print() const // const
    {
        std::cout << year << '/' << month << '/' << day;
    }
};

int main()
{
    Date today { 2020, 10, 14 }; // non-const

    today.print();  // ok: can call const member function on non-const object

    return 0;
}

Ponieważ funkcje składowe const mogą być wywoływane zarówno na obiektach const, jak i innych niż const, jeśli funkcja składowa nie modyfikuje stanu obiektu, należy to zrobić st.

Najlepsza praktyka

Funkcja składowa, która nie modyfikuje (i nigdy nie będzie) modyfikować stanu obiektu, powinna mieć postać const, aby można ją było wywoływać zarówno na obiektach const, jak i innych.

Uważaj, do jakich funkcji składowych stosujesz const . Gdy funkcja członkowska stanie się stałą, można ją wywołać na obiektach const. Późniejsze usunięcie const funkcji składowej spowoduje uszkodzenie dowolnego kodu wywołującego tę funkcję składową obiektu const.

Obiekty Const poprzez przekazywanie przez odwołanie do const

Chociaż tworzenie instancji zmiennych lokalnych const jest jednym ze sposobów tworzenia obiektów const, bardziej powszechnym sposobem uzyskania obiektu const jest przekazanie obiektu do funkcji przez odwołanie do const.

W lekcji 12.5 -- Przekazywanie przez lvalue reference, omówiliśmy zalety przekazywania argumentów typu klasy za pomocą stałej referencji, a nie wartości. Podsumowując, przekazanie argumentu typu klasy przez wartość powoduje utworzenie kopii klasy (co jest powolne) — w większości przypadków nie potrzebujemy kopii, odwołanie do oryginalnego argumentu działa dobrze i pozwala uniknąć tworzenia kopii. Zwykle tworzymy odwołanie jako const, aby umożliwić funkcji akceptowanie argumentów const lvalue i rvalue (np. literałów i obiektów tymczasowych).

Czy możesz zrozumieć, co jest nie tak z następującym kodem?

#include <iostream>

struct Date
{
    int year {};
    int month {};
    int day {};

    void print() // non-const
    {
        std::cout << year << '/' << month << '/' << day;
    }
};

void doSomething(const Date& date)
{
    date.print();
}

int main()
{
    Date today { 2020, 10, 14 }; // non-const
    today.print();

    doSomething(today);

    return 0;
}

Odpowiedź jest taka, że ​​wewnątrz doSomething() funkcji date jest traktowany jako obiekt stały (ponieważ został przekazany przez odwołanie do stałej). A za pomocą const date wywołujemy funkcję składową niebędącą stałą print(). Ponieważ nie możemy wywoływać funkcji składowych innych niż stałe na obiektach const, spowoduje to błąd kompilacji.

Poprawka jest prosta: make print() const:

#include <iostream>

struct Date
{
    int year {};
    int month {};
    int day {};

    void print() const // now const
    {
        std::cout << year << '/' << month << '/' << day;
    }
};

void doSomething(const Date& date)
{
    date.print();
}

int main()
{
    Date today { 2020, 10, 14 }; // non-const
    today.print();

    doSomething(today);

    return 0;
}

Teraz w funkcji doSomething(), const date będzie można pomyślnie wywołać funkcję składową const print().

Przeciążanie funkcji składowej const i non-const

Na koniec: chociaż nie robi się tego zbyt często, możliwe jest przeciążenie funkcji składowej, aby uzyskać stałą i inną wersję tej samej funkcji. Działa to, ponieważ kwalifikator const jest uważany za część sygnatury funkcji, więc dwie funkcje, które różnią się tylko swoją stałością, są uważane za odrębne.

#include <iostream>

struct Something
{
    void print()
    {
        std::cout << "non-const\n";
    }

    void print() const
    {
        std::cout << "const\n";
    }
};

int main()
{
    Something s1{};
    s1.print(); // calls print()

    const Something s2{};
    s2.print(); // calls print() const
    
    return 0;
}

Wypisuje:

non-const
const

Przeciążanie funkcji wersją stałą i inną niż stała jest zwykle wykonywane, gdy wartość zwracana musi różnić się stałością. Jest to dość rzadkie.

guest
Twój adres e-mail nie zostanie wyświetlony
Znalazłeś błąd? Zostaw komentarz powyżej!
Komentarze związane z poprawkami zostaną usunięte po przetworzeniu, aby pomóc zmniejszyć bałagan. Dziękujemy za pomoc w ulepszaniu witryny dla wszystkich!
Awatary z https://gravatar.com/ są połączone z podanym adresem e-mail.
Powiadamiaj mnie o odpowiedziach:  
276 Komentarze
Najnowsze
Najstarsze Najczęściej głosowane
Wbudowane opinie
Wyświetl wszystkie komentarze