понедельник, сентября 19, 2005

Использование const. Часть 1.

Есть две точки зрения на использование const.
Первая: const - это плохо. От него больше хлопот, чем пользы, ошибки какие-то странные вылезать начинают, лучше им не пользоваться.
Вторая: const - это хорошо. const не дает менять объекты, которые не должны меняться, таким образом оберегает от ошибок, его надо использовать везде где только можно.
Я придерживаюсь второй точки зрения.
В английской литературе можно часто встретить термины const correctness и const correct code, для кода, который корректно использует const. const имеет немного разный смысл в зависимости от того где находится. В C++ FAQ Lite вопросу Const correctness посвящен отдельный раздел.

Объявление переменных
Самый простой случай, обычная переменная. Переменная объявляется, тут же инициализируется, менять ее значение больше нельзя.
const int p=4;
p=5; //ошибка
Про использование const с указателями есть известный C++ паззл, который любят давать на собеседованиях при приеме на работу.
Чем отличаются
int *const p1
int const* p2
const int* p3
Правило тут такое: провести мысленно вертикальную черту по звездочке. То, что находится справа относится к переменной. То, что слева - к типу, на который она указывает. Вот например:
int *const p1
Cправа находится p1, и это p1 константа. Тип, на который p1 указывает, это int. Значит получился константный указатель на int. Его можно инициализировать лишь однажды и больше менять нельзя.
Нужно так:
int q=1;
int *const p1 = &q; //инициализация в момент объявления
*p1 = 5; //само число можно менять
Вот так компилятор не пропустит, потому что идет попытка присвоения константе:
int q=1;
int *const p1;
p1 = &q; //ошибка
Объявления
int const* p2
const int* p3
это по разному записанное одно и то же объявление. Указатель на целое, которое нельзя менять.
int q=1;
const int *p;
p = &q; //на что указывает p можно менять
*p = 5; //ошибка, число менять уже нельзя
Обычно в реальных программах используется вариант объявления const int, а int const используется, чтобы запутать на собеседовании.

const можно использовать со ссылками, чтобы через ссылку нельзя было поменять значение переменной.
int p = 4;
const int& x=p; //нельзя через x поменять значение p
x=5; //ошибка
Константная ссылка - это нонсенс. Она по определению константная. Компилятор скорее всего выдаст предупреждение, что он проигнорировал const.
int& const x; //не имеет смысла

Передача параметров в функцию
const удобен, если нужно передать параметры в функцию, но при этом надо обязательно знать, что переданный параметр не будет изменен.
void f1(const std::string& s);
void f2(const std::string* sptr);
void f3(std::string s);
В первой и второй функции попытки изменить строку будут пойманы на этапе компиляции. В третьем случае в функции будет происходить работа с локальной копией строки, исходная строка не пострадает.


Приведение Foo** к const Foo** приводит к ошибке
Потому что такое приведение может позволить менять объекты, которые константны.
class Foo
{
...
public:
void modify(); //вносит какие-либо изменения
};

int main()
{
const Foo x;
Foo* p;
const Foo** q = &p; // q теперь указывает на p; и это ошибка
*q = &x; // p теперь указывает на x
p->modify(); // попытка изменить const Foo!!
}
Самый простой способ это исправить это поменять const Foo** на const Foo* const*.

В следующих постах речь пойдет об использовании const для данных и функций классов, а также о mutable и const_cast.

Ссылки по теме:
[18] Const correctness, C++ FAQ Lite
comp.lang.c++.moderated is const correctness a dogma ?

Technorati tag:

28 комментариев:

  1. Анонимный25/12/07 12:43

    Прекрасно изложен материал, спасибо!

    ОтветитьУдалить
  2. Анонимный29/1/08 10:58

    Сложные вещи простыми словами!!! Спасибо! Большое прибольшое спасибо!

    ОтветитьУдалить
  3. Анонимный26/6/09 13:27

    Спасибо!!

    ОтветитьУдалить
  4. Мне, кажется, имеет смысл еще осветить следующую ситуацию:

    class A
    {
    private:
    std::vector {B*} lData;
    public:
    void addData(B* data);
    B* getData(const int i) const;
    }

    Примечание: Не смог написать треугольные скобочки при инстанциировании шаблона - написал {}
    Нужно добавить, что класс A устроен так, что элементы lData вообще нет смысла менять или даже удалять из списка. Например, если класс A некая функциональная обертка над B. Но создавать вектор указателей на константы плохо (в какой-то книге об этом говорилось). Тут нужен компромисс :) и поэтому не смотря на то, что addData не меняет передаваемый указатель и объект на который он указывает const ставить не стоит.

    ОтветитьУдалить
  5. Уважаемая Алёна!
    Не могли бы вы помочь разобраться с следующей ситуацией?

    const int x=10;
    int* p=const_cast(&x);
    *p=11;
    cout << x << endl;

    На экране 10, но непонятно, почему... Что тут происходит? Куда указывает p?
    Он точно указывает не на x, т.к. добавив cout << *p << endl; на экране будет как 10, так и 11. Значит p указывает куда-то кроме икса, но куда? Кто выделял память под эту 11?
    Я предполагаю, что p указывает на временную переменную, созданную во время вычисления выражения с приведением типа, но... не уверен :(
    Разве временная переменная не должна быть уничтожена после вычисления выражения?

    ОтветитьУдалить
  6. 2DWord:

    На экране 10, но непонятно, почему... Что тут происходит? Куда указывает p?

    Вы снимаете const с переменной, которая была изначально объявлена как const. По Стандарту это ведет к неопределенному поведению (7.1.5.1).

    ОтветитьУдалить
  7. Хм.. вообще логично :)
    Благодарю за помощь!
    И спасибо за статью! Побудила разобраться в тонкостях)

    ОтветитьУдалить
  8. Возникло недопонимае вопроса
    почему
    const char *p1="maus";
    char const *p2="elefant";

    p1="rat";//ошибка
    p2="krokodail";//ошибки нет

    если это важно компилятор minGW

    ОтветитьУдалить
  9. Прошу прощения , теперь разобрался пусть и перечитать пришлось несколько раз если я всеже неправ то прошу меня поправить
    const char *p1="maus";
    p1="rat";->видимо здесь происходит не изменение даных а присвоение нового адреса указателю,придется еще несколько раз прочитать работу указателей со строками

    ОтветитьУдалить
  10. 2leg:

    Возникло недопонимае вопроса
    почему


    Не должно быть разницы, minGW меня удивил. Какие-то компиляторы это копиляют, какие-то нет. Такое присваивание - не очень хорошая идея. В случае p1 будет создана новая строка "rat", указателей на первую строку не останется. Причем модифицирование такой строки ведет к undefined behavior.
    Если вам нужно просто работать со строками, используйте std::string.

    ОтветитьУдалить
  11. 2leg:
    видимо здесь происходит не изменение даных а присвоение нового адреса указателю

    Да, именно так

    ОтветитьУдалить
  12. Спасибо за статью, действительно помогла разобратся с непонятными моментами.

    ОтветитьУдалить
  13. Анонимный4/2/11 07:48

    Спасибо за статью.
    А как использовать мнемоническое правило с чертой по звездочке в случае двойных и более указателей как на элементарные типы, так и на объекты, да еще и в аргументах функций(методов)?

    ОтветитьУдалить
  14. nightshad0w
    Спасибо за статью.

    пожалуйста :-)

    А как использовать мнемоническое правило с чертой по звездочке в случае двойных и более указателей как на элементарные типы, так и на объекты, да еще и в аргументах функций(методов)?

    Вот тут про это много написано:
    http://www.unixwiz.net/techtips/reading-cdecl.html

    ОтветитьУдалить
  15. Здорово. Всё ясно =)

    ОтветитьУдалить
  16. Анонимный11/3/11 15:14

    Алена, вы прсто умничка!

    ОтветитьУдалить
  17. Анонимный2/6/11 16:51

    Большое спасибо,помогло!

    ОтветитьУдалить
  18. Интересно, они серьёзно все считают, что const обязательно надо использовать?
    Ребята, не верьте никому, даже мне.
    (Мюллер)

    ОтветитьУдалить
  19. Alexandr8/4/14 15:16

    g++.exe p.cpp -o p.exe -m32 -std=c++11

    const char *p1="maus";
    char const *p2="elefant";

    p1="rat";//ошибки нет
    p2="krokodail";//ошибки нет

    ;)

    ОтветитьУдалить
  20. Анонимный20/1/15 20:31

    На экране 10, но непонятно, почему... Что тут происходит? Куда указывает p?
    Готов поспорить, что если добавить volatile const int x = 10;
    то на экран будет выведено 11. (Не смотря на undefined behavior)

    ОтветитьУдалить
  21. Готов поспорить, что если добавить volatile const int x = 10;
    то на экран будет выведено 11. (Не смотря на undefined behavior)


    Ты прав. Сейчас проверил - и правда выводится 11

    ОтветитьУдалить
  22. Анонимный11/5/15 22:02

    Алена я вас люблю!!

    ОтветитьУдалить
  23. Алена, спасибо большое за статью!

    Скажите, а как быть с этим вот:
    int * const * p5

    Я не могу понять.. это указатель на константный указатель или константный указатель на указатель? По сути должно быть верным второе (константный указатель на указатель, иначе зачем использовать const в данном случае). Но не до конца уверена, что я права.

    ОтветитьУдалить
  24. Unknown

    Я не могу понять.. это указатель на константный указатель или константный указатель на указатель?

    Это указатель на константный указатель. По аналогии с int const* p2.

    Это всегда можно проверить, написать небольшой тест и попробовать изменить p5 и то, куда оно указывает.

    Зачем - отдельный вопрос, надо по коду смотреть.

    ОтветитьУдалить
  25. Анонимный11/11/16 18:59

    Хочу заметить:

    из статьи:
    int q=1;
    const int *p;
    p = &q; //на что указывает p можно менять
    *p = 5; //ошибка, число менять уже нельзя

    предложение:
    *p = 5; //ошибка значение переменной q нельзя менять с помощью указателя p
    int *p2 = &q;
    *p2 = 5; //ошибки нет значение переменной q можно!!! менять с помощью указателя p2

    ОтветитьУдалить
  26. Анонимный17/11/16 13:43


    Хочу заметить:

    из статьи:
    int q=1;
    const int *p;
    p = &q; //на что указывает p можно менять
    *p = 5; //ошибка, число менять уже нельзя

    предложение:
    *p = 5; //ошибка значение переменной q нельзя менять с помощью указателя p
    int *p2 = &q;
    *p2 = 5; //ошибки нет значение переменной q можно!!! менять с помощью указателя p2


    предложение получше:
    *p = 5; //ошибка значение переменной q нельзя менять с помощью указателя p
    q = 5; //ошибки нет значение переменной q можно!!! менять с помощью q!!!! ЧУДО.

    ОтветитьУдалить
  27. Анонимный3/12/16 11:41

    Спасибо большое! Очень доступно!

    ОтветитьУдалить