Почему const-корректность специфична для C++?

отказ от ответственности: я знаю, что есть два вопроса о полезности const-корректности, однако никто не обсуждал, как const-корректность необходима в C++ в отличие от других языков программирования. кроме того, я не доволен ответами на эти вопросы.

я использовал несколько языков программирования, и одна вещь, которая меня беспокоит в C++, - это понятие const-correctness. Нет такого понятия в Java, C#, Python, Ruby, Visual Basic, так далее., это кажется очень специфичным для C++.

прежде чем вы обратитесь ко мне в C++ FAQ Lite, я прочитал его, и это не убеждает меня. Совершенно действительные, надежные программы написаны на Python все время, и нет ключевого слова const или эквивалента. В Java и C# объекты могут быть объявлены final (или const), но нет функций-членов const или параметров функции const. Если функции не требуется изменять объект, она может использовать интерфейс, предоставляющий доступ только для чтения объект. Этот метод также можно использовать в C++. В двух реальных системах C++, над которыми я работал, было очень мало использования const, и все работало нормально. Поэтому я далек от того, чтобы позволить const загрязнять кодовую базу.

Мне интересно, что в C++ делает const необходимым,в отличие от других языков программирования.

до сих пор я видел только один случай, когда const должны будет используется:

#include <iostream>

struct Vector2 {
    int X;
    int Y;
};

void display(/* const */ Vector2& vect) {
    std::cout << vect.X << " " << vect.Y << std::endl;
}

int main() {
    display(Vector2());
}

компиляция этого с помощью комментария const принимается Visual Studio, но с предупреждением C4239, нестандартные расширения. Итак, если вы хотите синтаксической краткости прохождения во временных, избегая копий и оставаясь стандартно-совместимыми, вы должны пройти мимо ссылки const, никак не обойти ее. Тем не менее, это больше похоже на причуду, чем на фундаментальную причину.

в противном случае действительно нет ситуации, когда const должен использоваться, кроме при взаимодействии с другим кодом, использующим const. Const кажется мне не более чем самодовольной чумой, которая распространяется на все, к чему прикасается:

причина, по которой const работает в C++ потому что вы можете отбросить его. Если вы я не мог выбросить это из головы, тогда это был твой мир. будет отстойно. Если вы объявите метод это занимает const Bla, вы можете пройти это не const Bla. Но если это иначе вы не можете. Если вы объявить метод, который принимает non-const Bla, ты не можешь пройти мимо. const Bla. Так что теперь ты застрял. Так вы постепенно нужна версия const все, что не является const, и вы в конечном итоге с теневым миром. В C++ вы сойдет с рук, потому что как с что-нибудь в C++ это чисто необязательно хотите вы этот чек или нет. Вы можете просто выбить constness прочь если тебе не нравится.

Андерс Хейльсберг (архитектор c#), выбор дизайна CLR

14 ответов


const correctness предоставляет два заметных преимущества C++, о которых я могу думать, один из которых делает его довольно уникальным.

  • это позволяет широко распространенные понятия изменяемых / неизменяемых данных, не требуя кучу интерфейсов. Отдельные методы могут быть аннотированы относительно того, могут ли они выполняться на объектах const, и компилятор обеспечивает это. Да, иногда это может быть хлопот, но если вы используете его последовательно и не используете const_cast вы проверили безопасность компилятора с помощью с уважением к изменяемым и неизменяемым данным.
  • если объект или элемент данных const, компилятор может разместить его в памяти только для чтения. Это особенно важно для встроенных систем. C++ поддерживает это; немногие другие языки делают это. Это также означает, что в общем случае вы не можете безопасно бросить const, хотя на практике вы можете делать это в большинстве сред.

C++ - не единственный язык с корректностью const или что-то в этом роде. OCaml и Стандартные ML имеют схожую концепцию с другой терминологией-почти все данные неизменяемы (const), и когда вы хотите, чтобы что-то было изменяемым, вы используете другой тип (a ref type)для этого. Таким образом, он просто уникален для C++ на соседних языках.

наконец, идя в другом направлении: были времена, когда я хотел const на Java. final иногда не доходит до создания явно неизменяемых данных (особенно неизменяемых представлений изменяемых data) и не хотят создавать интерфейсы. Посмотрите на Немодифицируемую поддержку коллекции в API Java и тот факт, что он проверяет только во время выполнения, разрешено ли изменение для примера того, почему const полезен (или, по крайней мере, структура интерфейса должна быть deepend, чтобы иметь List и MutableList) - нет причин, по которым попытка мутировать неизменяемую структуру не может быть ошибкой типа компиляции.


Я не думаю, что кто-то утверждает, что const-правильность "необходима". Но, опять же, занятия на самом деле тоже не нужны, не так ли? То же самое касается пространств имен, исключений,... вы все поняли.

Const-correctness помогает поймать ошибки во время компиляции, и поэтому это полезно.


const-это способ выразить что-то. Это было бы полезно на любом языке, если бы вы думали, что это важно выразить. У них нет этой функции, потому что языковые дизайнеры не нашли их полезными. Если бы функция была там, это было бы примерно так же полезно, я думаю.

Я вроде как думаю об этом, как спецификации броска в Java. Если они вам нравятся, вы, вероятно, хотели бы их на других языках. Но создатели других языков не думали, что дело в этом. важный.


вы правы, const-корректность не нужна. Вы, конечно, можете написать весь свой код без ключевого слова const и заставить вещи работать, как и в Java и Python.

но если вы это сделаете, вы больше не получите помощь компилятора в проверке нарушений const. Ошибки, о которых компилятор сказал бы вам во время компиляции, теперь будут найдены только во время выполнения, если вообще будут найдены, и поэтому вам потребуется больше времени для диагностики и исправления.

, попытка ниспровергнуть или избежать функции const-correctness просто делает вещи сложнее для себя в долгосрочной перспективе.

Ну, мне понадобится 6 лет, чтобы действительно понять, но теперь я могу, наконец, ответить на свой собственный вопрос.

причина, по которой C++ имеет "const-правильность"и что Java, C# и т. д. не знаю, что C++ поддерживает только типы значений, и эти другие языки поддерживают только или, по крайней мере, по умолчанию ссылка типа.

давайте посмотрим, как C#, язык, который по умолчанию использует ссылочные типы, имеет дело с неизменяемостью при использовании типов значений. Скажем у вас есть изменяемый тип значения и другой тип, который имеет поле только для чтения этого типа:

struct Vector {
    public int X { get; private set; }
    public int Y { get; private set; }
    public void Add(int x, int y) {
        X += x;
        Y += y;
    }
}

class Foo {
    readonly Vector _v;
    public void Add(int x, int y) => _v.Add(x, y);
    public override string ToString() => $"{_v.X} {_v.Y}";
}

void Main()
{
    var f = new Foo();
    f.Add(3, 4);
    Console.WriteLine(f);
}

что должен делать этот код?

  1. компилироваться
  2. печать "3, 4"
  3. печать "0, 0"

ответ #3. C# пытается выполнить ваше ключевое слово "readonly", вызывая метод Add на выброшенной копии объекта. Это странно, да, но какие еще варианты у него есть? Если он вызывает метод на оригинале Вектор, объект будет меняться, нарушая "только чтение" - Несс поля. Если он не компилируется, то члены типа значения readonly довольно бесполезны, потому что вы не можете вызвать на них какие-либо методы из страха, что они могут изменить объект.

Если бы только мы могли пометить, какие методы безопасны для вызова только экземпляров readonly... Подождите, это именно то, что методы const в C++!

C# не беспокоится о методах const, потому что мы не используем типы значений в C#; мы просто избегайте изменяемых типов значений (и объявите их "злыми", см. 1, 2).

кроме того, ссылочные типы не страдают от этой проблемы, потому что когда вы отмечаете переменную ссылочного типа как readonly, то, что readonly является ссылкой, а не сам объект. Это очень легко для компилятора, он может пометить любое назначение как ошибку компиляции, кроме как при инициализации. Если все, что вы используете, это ссылочные типы и все ваши поля и переменные читаем только, вы получаете неизменяемость везде при небольших синтаксических затратах. F# работает полностью так. Java избегает проблемы, просто не поддерживая пользовательские типы значений.

C++ не имеет понятия "ссылочные типы", только" типы значений " (на C#-жаргоне); некоторые из этих типов значений могут быть указателями или ссылками, но, как и типы значений в C#, ни один из них собственные их хранения. Если c++ лечить "const" и о его видах как в C# лечит "только для чтения" для типов значений, это будьте очень запутанными, как показывает пример выше, не обращайте внимания на неприятное взаимодействие с конструкторами копирования.

таким образом, C++ не создает выброшенную копию, потому что это создаст бесконечную боль. Это также не запрещает вам вызывать какие-либо методы для членов, потому что, ну, язык не был бы очень полезен тогда. Но он все еще хочет иметь какое-то понятие "только для чтения" или "постоянства".

C++ пытается найти средний путь, заставляя вас помечать, какие методы безопасны для вызова на членах const, а затем он доверяет вам, чтобы Вы были верны и точны в своей маркировке и вызывали методы непосредственно на исходных объектах. Это не идеально-это многословно, и Вам разрешено нарушать const-ness столько, сколько вам нравится, - но это, возможно, лучше, чем все остальные варианты.


программирование-это запись на языке, который в конечном итоге будет обработан компьютером, но это одновременно способ общения с компьютером и другими программистами в том же проекте. Когда вы используете язык, вы ограничены понятиями, которые могут быть выражены в нем, и const-это еще одно понятие, которое вы можете использовать для описания своей проблемы и своего решения.

Constantness позволяет четко от дизайна до кода одно понятие, что и другие языков не хватает. Поскольку вы пришли из языка, в котором его нет, вы можете показаться озадаченным понятием, которое никогда не использовали-если вы никогда не использовали его раньше, насколько важным оно может быть?

язык и мысль тесно связаны. Вы можете выражать свои мысли только на том языке, на котором говорите, но язык также изменяет способ вашего мышления. Тот факт, что у вас не было ключевого слова const на языках, с которыми вы работали, означает, что вы уже нашли другие решения для того же проблемы, и эти решения кажутся вам естественными.

в вопросе вы утверждали, что можете предоставить не мутирующий интерфейс, который может использоваться функциями, которым не нужно изменять содержимое объектов. Если вы задумаетесь об этом на секунду, это же предложение говорит вам, почему const-это концепция, с которой вы хотите работать. Необходимость определить не мутирующий интерфейс и реализовать его в своем классе-это работа вокруг того факта, что вы не можете выразить эту концепцию в своем язык.

Constantness позволяет выражать эти понятия на языке, который может понять компилятор (и другие программисты). Вы устанавливаете компромисс относительно того, что вы будете делать с параметрами, которые вы получаете, ссылками, которые вы храните, или определяете ограничения на то, что пользователи вашего класса могут делать с предоставленными вами ссылками. Практически каждый нетривиальный класс может иметь состояние, представленное атрибутами, и во многих случаях инварианты должны быть хранившийся. Язык позволяет определить функции, которые предоставляют доступ к некоторым внутренним данным, и в то же время ограничивает доступ к представлению только для чтения, которое гарантирует, что внешний код не нарушит ваши инварианты.

Это концепция, которую я пропускаю больше при переходе на другие языки. Рассмотрим сценарий, в котором у вас есть класс C, который, среди прочего, имеет атрибут A типа A, который должен быть видимым для внешнего кода (пользователи вашего класса должны иметь возможность запрашивать некоторую информацию о a). Если тип A имеет любую операцию мутации, затем, чтобы пользовательский код не менял ваше внутреннее состояние, вы должны создать копию a и вернуть ее. Программист класса должен знать, что копия должна быть выполнена и должна выполнить (возможно, дорогостоящую) копию. С другой стороны, если бы вы могли выразить постоянство на языке, вы могли бы просто вернуть постоянную ссылку на объект (фактически ссылку на постоянный вид объекта) и просто вернуть внутренний элемент. Это позволит пользовательский код для вызова любого метода объекта, который проверяется как не мутирующий, сохраняя инварианты класса.

проблема / преимущество, все зависит от точки зрения, постоянства в том, что она вирусная. Когда вы предлагаете постоянную ссылку на объект, можно вызвать только те методы, которые помечены как не мутирующие, и вы должны сообщить компилятору, какой из методов имеет это свойство. Когда вы объявляете константу метода, вы сообщаете компилятору, что пользовательский код вызовы этого метода сохранят состояние объекта. Когда вы определяете (реализуете) метод, который имеет постоянную подпись, компилятор напомнит вам о вашем обещании и фактически потребует, чтобы вы не изменяли данные внутренне.

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

в этом context, const_cast никогда не следует использовать, так как результаты могут привести вас в область неопределенного поведения (как с точки зрения языка: объект может быть только для чтения, так и с точки зрения программы: вы можете нарушать инварианты в других классах). Но это, конечно,вы уже знаете, если Вы читаете C++FAQ lite.

в качестве примечания, последнее ключевое слово в Java действительно не имеет ничего общего с ключевым словом const в C++, когда вы имеете дело со ссылками (в C++ ссылки или указатели). Ключевое слово final изменяет локальную переменную, на которую оно ссылается, будь то базовый тип или ссылка, но не является модификатором указанного объекта. То есть вы можете вызвать методы мутации через конечную ссылку и, таким образом, предоставить изменения в состоянии упомянутого объекта. В C++ ссылки всегда постоянны (их можно привязать только к объекту/переменной во время построения), а ключевое слово const изменяет способ работы пользовательского кода с указанным объектом. (В в случае указателей вы можете использовать ключевое слово const как для datum, так и для указателя: X const * const объявляет постоянный указатель на константу X)


вы также хотите использовать const в методах, чтобы воспользоваться преимуществами оптимизации возвращаемого значения. См. Scott Meyers более эффективный элемент c++ 20.


Если вы пишете программы для встроенных устройств с данными во FLASH или ROM, вы не можете жить без const-корректности. Это дает вам возможность контролировать правильную обработку данных в различных типах памяти.


В C, Java и c# вы можете сказать, посмотрев на сайт вызова, если переданный объект может быть изменен функцией:

  • в Java вы знаете, что это наверняка может быть.
  • В C вы знаете, это может быть только если есть '&', или эквивалент.
  • В c# вам нужно сказать " ref " на сайте вызова тоже.

в C++ в общем случае вы не можете этого сказать, так как не-const ссылочный вызов выглядит идентичным pass-by-value. Наличие ссылок const позволяет установить и обеспечению соблюдения Конвенции гр.

Это может существенно изменить читаемость любого кода, вызывающего функции. Этого, вероятно, достаточно, чтобы оправдать языковую особенность.


Anders Hejlsberg (c# architect):... Если вы объявляете метод, который принимает non-const Bla, вы не можете передать его const Bla. Так что теперь ты застрял. Поэтому вам постепенно нужна версия const всего, что не является const, и вы в конечном итоге получаете теневой мир.

Итак, снова: если вы начали использовать "const" для некоторых методов, вы обычно вынуждены использовать это в большинстве своего кода. Но время, затраченное на поддержание (ввод, перекомпиляция, когда отсутствует какая-то константа и т. д.) из const-корректность в коде кажется больше, чем для исправления возможных (очень редких) проблем, вызванных вообще не использованием const-корректности. Таким образом, отсутствие поддержки const-корректности в современных языках (например, Java, C#, Go и т. д.) может привести к незначительному сокращению времени разработки для того же качества кода.

запрос на расширение для реализации корректности const существовал в процессе сообщества Java с 1999 года, но был закрыт в 2005 году из-за вышеупомянутого " загрязнения const" а также причины совместимости:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4211070

хотя язык C# не имеет конструкции корректности const, но подобная функциональность, возможно, скоро появится в "Microsoft Code Contracts" (библиотека + инструменты статического анализа) для .NET Framework с использованием атрибутов [Pure] и [Immutable]:чистые функции в C#


этой разговоры и видео С Херб Саттер объясняет новые коннотации const Что касается безопасности резьбы.

константность возможно, раньше вам не приходилось слишком беспокоиться, но с C++11, Если вы хотите написать потокобезопасный код, вам нужно понять значение const и mutable


на самом деле, это не так... во всяком случае, не совсем.

в других языках, особенно функциональных или гибридных языках, таких как Haskell, D, Rust и Scala, у вас есть концепция изменчивости: переменные могут быть изменяемыми или неизменяемыми и обычно неизменяемы по умолчанию.

Это позволяет вам (и вашему компилятору/интерпретатору) лучше рассуждать о функциях: если вы знаете, что функция принимает только неизменяемые аргументы, то вы знаете, что функция не является той, которая мутирует ваш переменная и вызывает ошибку.

C и c++ делают что-то подобное с помощью const, за исключением того, что это гораздо менее твердая гарантия: неизменность не применяется; функция дальше по стеку вызовов может отбросить константу и мутировать ваши данные, но это было бы преднамеренным нарушением контракта API. Так что намерение или лучше всего, чтобы он работал так же, как неизменяемость на других языках.

все, что сказано, C++ 11 теперь имеет фактическую изменяемую ключевое слово, наряду с более ограниченным ключевым словом const.


ключевое слово const в C++ (применительно к параметрам и объявлениям типов) - это попытка удержать программистов от стрельбы с большого пальца и вынимания всей ноги в процессе.

основная идея состоит в том, чтобы обозначить что-то как "не может быть изменено". Тип const не может быть изменен (по умолчанию). Константный указатель не может указывать на новое место в памяти. Просто, да?

Ну, вот где приходит const правильность. Вот некоторые из возможных комбинации, в которых вы можете оказаться при использовании const:

переменная const Означает, что данные, помеченные именем переменной, не могут быть изменены.

указатель на переменную const Предполагает, что указатель может быть изменен, но сами данные не может.

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

указатель const на переменную const Подразумевает, что ни указатель, ни данные, на которые он указывает, не могут быть изменены.

вы видите, как некоторые вещи могут быть Гуфи есть? Вот почему, когда вы используете const, важно быть правильным, в какой const вы маркируете.

дело в том, что это просто компиляции взломать. Маркировка просто сообщает компилятору, как интерпретировать инструкции. Если вы отбросьте const, вы можете делать все, что захотите. Но вам все равно придется вызывать методы, которые имеют требования const с типами, которые приведены соответствующим образом.


например, у вас есть функция:

void const_print(const char* str)
{
    cout << str << endl;
}

другой способ

void print(char* str)
{
    cout << str << endl;
}

в главном:

int main(int argc, char **argv)
{
    const_print("Hello");
    print("Hello");        // syntax error
}

это потому, что "hello" является указателем const char, строка (C-style) помещается в память только для чтения. Но это полезно в целом, когда программист знает, что значение не будет изменено.Поэтому, чтобы получить ошибку компилятора вместо ошибки сегментации. Как в незапланированных заданиях:

const int a;
int b;
if(a=b) {;} //for mistake

так как левый операнд является const int.