Const (программирование)

Эта статья находится на начальном уровне проработки, в одной из её версий выборочно используется текст из источника, распространяемого под свободной лицензией
Материал из энциклопедии Руниверсалис

В языках программирования C, C++, C# и D const является квалификатором типа:[a] ключевое слово применяется к типу данных, показывая, что данные константны (неизменяемы). Это может быть использовано при объявлении (декларировании) констант. Отличительная особенность const в C-подобных языках программирования проявляется при его комбинировании с типами данных, что дает сложное поведение в сочетании с указателями, ссылками, составными типами данных и при проверке типов.

Введение

При объявлении[b] объектов с использованием const их значения константны (неизменяемы), в отличие от перeменных. Этот основной сценарий использования — объявление констант — имеет параллели во многих языках.

Однако в отличие от других языков в семье языков C const является частью типа, а не объекта. Например, в C const int x = 1; объявляет объект x типа const int, где const — часть типа. Это может читаться «(const int) x» — в то время как в Ada X : constant INTEGER := 1_ объявляет константу (вид объекта) X типа INTEGER, т. е. constant является частью объекта, а не типа.

Появляются два тонких момента. Во-первых, const может быть частью более сложных типов. Например, int const * const x; (выражение читается от переменной x в противоположную сторону) объявляет константный указатель на константное целое число, в то время как int const * x; объявляет переменную-указатель на константное целое, а int * const x; объявляет константный указатель на переменное целое. Во-вторых, поскольку const является частью типа, это используется при проверке типов. Например, следующий код некорректен:

void f(int& x);
//...
const int i;
f(i);

потому что аргумент, передаваемый в f, должен быть ссылочной переменной на целое, а i — константное целое. Требование такого соответствия — форма обеспечения правильности программы, также известное как const-овая правильность (англ. const-correctness). Это дает возможность контрактного проектирования, где у функций в качестве части сигнатуры типа указывается, будут ли они изменять свои аргументы или нет, и изменяемы (неконстантны) ли их возвращаемые значения. Такая проверка типов интересна прежде всего для указателей и ссылок (т. е. когда параметры передаются по ссылке) — а не для основных типов, таких как целые, — а также для составных типов данных или шаблонизированных типов, таких как контейнеры. Вследствие неявного преобразования типов при выполнении программы const может быть опущен.

Следствия

При сохранении в памяти компьютера, константность не накладывает на значение ограничения на запись. const — это скорее конструкция времени компиляции, которую программист потенциально может использовать, однако это необязательно. Стоит обратить внимание, что в случае предопределенных строковых литералов (таких как const char *) константное значение в языке СиСи++) обычно неперезаписываемо, так как оно может храниться в сегменте памяти с запретом записи.

Другие варианты использования

В дополнение к этому как const может быть объявлена функция-член (нестатическая). В этом случае указатель this внутри такой функции будет иметь тип object_type const * const вместо object_type * const. Это означает, что неконстантные по отношению к этому объекту функции изнутри такой функции вызваны быть не могут, также не могут быть модифицированы поля класса. В C++ поле класса может быть объявлено как mutable (изменяемое), что означает, что это ограничение к нему не относится. В некоторых случаях это может быть полезно, например, при кешировании, подсчёте ссылок и синхронизации данных. В этих случаях логический смысл (состояние) объекта неизменяем, но объект физически неконстантен, так как его поразрядное представление может измениться.

Синтаксис

В C, C++, и D все типы данных, включая те, которые определены пользователем, могут быть объявлены const, и «const-овая правильность» предполагает, что все переменные или объекты должны быть объявлены таковыми, если их не нужно модифицировать. Такое предусмотрительное использование const делает значения переменных "простыми для понимания, отслеживания, и обдумывания"[1], таким образом, читаемость и понятность увеличиваются и делают работу в командах и поддержку кода проще, потому что это предоставляет информацию о надлежащем использовании их значений. Это может помочь компилятору так же, как и разработчику при размышлениях над кодом. Это также может позволить оптимизирующему компилятору генерировать более эффективный код[2].

Простые типы данных

Для простых типов данных (неуказателей) применение квалификатора const очевидно. Его можно указывать с любой стороны типа по историческим причинам (const char foo = 'a'; эквивалентно char const foo = 'a';). В некоторых реализациях использование const с двух сторон типа (например, const char const) генерирует предупреждение, но не ошибку.

Указатели и ссылки

Для указателей и ссылок результирующий эффект const более запутан: и сам указатель, и значение, на которое он указывает, или оба могут быть объявлены как const. Более того, синтаксис также может сбивать с толку.

Указатель может быть объявлен const-указателем на перезаписываемое значение (type * const x;), или перезаписываемым указателем на const-значение (type const * x; //или: const type * x;), или const-указателем на const-значение (type const * const x;). const-указателю нельзя переприсвоить ссылку на другой объект с первоначального, но он (указатель) может быть использован для изменения значения, на которое он указывает (такое значение называется «значение по указателю» (англ. pointee)). Таким образом, синтаксис ссылочных переменных — альтернативный синтакс const-указателей. С другой стороны, указателю на const-объект можно переприсвоить ссылку для указания на другую область памяти (которая должна содержать объект того же или приводимого типа), но его нельзя будет использовать, чтобы менять значения в памяти, на которые он указывает. Также может быть определен const-указатель на const-объект, который нельзя использовать, чтобы изменять значение по нему, и которому нельзя переприсвоить ссылку на другой объект.

Эти тонкости иллюстрирует следующий код:

void Foo (
 int * ptr,
 int const * ptrToConst,
 int * const constPtr,
 int const * const constPtrToConst)
{
    *ptr = 0; //Порядок: меняет данные по указателю.
    ptr = NULL; //Порядок: меняет указатель.
    
    *ptrToConst = 0; //Ошибка! Нельзя менять данные по указателю.
    ptrToConst = NULL; //Порядок: меняет указатель.
    
    *constPtr = 0; //Порядок: меняет данные по указателю.
    constPtr  = NULL; //Ошибка! Нельзя менять указатель.
    
    *constPtrToConst = 0; //Ошибка! Нельзя менять данные по указателю.
    constPtrToConst  = NULL; //Ошибка! Нельзя менять указатель.
}

Соглашения по записи в языке C

В соответствие с обычными соглашениями языка Си об объявлениях последние указываются за предполагаемым использованием, а звёздочка у указателя к нему ставится вплотную, указывая на разыменование. Например, в объявлении int *ptr разыменованная форма *ptr является целым (int), а ссылочная форма ptr — указателем на целое. Таким образом, const модифицирует имя переменной справа от себя.

Соглашение в языке Си++ – наоборот, связывать * с типом (т. е. int* ptr) и читать, что const модифицирует тип слева от себя. Поэтому int const * ptrToConst можно прочитать или как «*ptrToConst – это int const» (значение по указателю неизменяемо), или как «ptrToConst – это int const *» (указатель на неизменяемое целое значение).

Таким образом:

int *ptr; //"*ptr" -- целое значение.
int const *ptrToConst; //"*ptrToConst" -- константа ("int" -- целая).
int * const constPtr; //"constPtr" -- константа ("int *" -- указатель на целое).
int const * const constPtrToConst; //"constPtrToConst" -- константа (указатель),
                                   //как и "*constPtrToConst" (значение).

Примечания

  1. In D the term type constructor is used instead of type qualifier, by analogy with constructors in object-oriented programming.
  2. Formally when the const is part of the outermost derived type in a declaration; pointers complicate discussion.
  1. Herb Sutter and Andrei Alexandrescu (2005).
  2. Why is the kfree() argument const? (недоступная ссылка). lkml.org (12 января 2013). Дата обращения: 9 января 2016. Архивировано 4 марта 2016 года.