понедельник, сентября 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 коммент.:

siegerstein комментирует...

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

Анонимный комментирует...

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

Анонимный комментирует...

Спасибо!!

Sergey комментирует...

Спасибо!

RS комментирует...

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

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

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

DWord комментирует...

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

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 указывает на временную переменную, созданную во время вычисления выражения с приведением типа, но... не уверен :(
Разве временная переменная не должна быть уничтожена после вычисления выражения?

Alena комментирует...

2DWord:

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

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

DWord комментирует...

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

leg комментирует...

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

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

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

leg комментирует...

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

Alena комментирует...

2leg:

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


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

Alena комментирует...

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

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

leg комментирует...

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

nightshad0w комментирует...

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

Alena комментирует...

nightshad0w
Спасибо за статью.

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

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

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

Lex_87 комментирует...

Здорово. Всё ясно =)

Анонимный комментирует...

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

Анонимный комментирует...

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

Андрей Иванов комментирует...

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

Alexandr комментирует...

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

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

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

;)

Анонимный комментирует...

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

Пётр комментирует...

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


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

Анонимный комментирует...

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

Unknown комментирует...

Алена, спасибо большое за статью!

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

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

Alena комментирует...

Unknown

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

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

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

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

Анонимный комментирует...

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

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

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

Анонимный комментирует...


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

из статьи:
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!!!! ЧУДО.

Анонимный комментирует...

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