Подтвердить что ты не робот

Почему const-correctness специфична для С++?

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

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

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

Мне интересно, что это на С++, что делает 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 Bla, вы можете пройти это неконстантная Бла. Но если это иначе вы не можете. если ты объявить метод, который принимает не-const Bla, вы не можете передать его const Bla. Так что теперь ты застрял. Так что вы постепенно требуется версия const все, что не const, и вы в конечном итоге с теневым миром. В С++ вы уйти с ним, потому что как с все на С++ это чисто необязательно хотите ли вы эту проверку или нет. Вы можете просто ударить сотню если вам это не нравится.

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

4b9b3361

Ответ 1

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

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

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

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);
}

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

  • не удается скомпилировать
  • print "3, 4"
  • print "0, 0"

Ответ №3. С# пытается почтить ваше ключевое слово "readonly", вызывая метод "Добавить" в отбрасываемой копии объекта. Это странно, да, но какие у него есть другие варианты? Если он вызывает метод в исходном векторе, объект изменится, нарушив "readonly" -ness поля. Если это не удается скомпилировать, то члены типа readonly типа типа бесполезны, потому что вы не можете ссылаться на какие-либо методы на них, из страха они могут изменить объект.

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

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

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

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

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

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

Ответ 2

Корректность Const дает два важных преимущества для С++, о которых я могу думать, один из которых делает его довольно уникальным.

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

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

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

Ответ 3

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

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

Ответ 4

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

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

Ответ 5

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

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

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

Ответ 6

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

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

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

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

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

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

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

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

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

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

Ответ 7

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

Ответ 8

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

Ответ 9

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

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

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

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

Ответ 10

Андерс Хейлсберг (архитектор С#):... Если вы объявляете метод, который принимает неконстантную Bla, вы не можете передать ему const Bla. Так что теперь ты застрял. Поэтому вам постепенно нужна версия const, которая не является константой, и вы получаете теневой мир.

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

Билет запроса на повышение для реализации корректности состязания существовал в Процессе сообщества Java с 1999 года, но был закрыт в 2005 году из-за вышеупомянутого "загрязнения СОСТОЯНИЕМ", а также соображений совместимости: http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4211070

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

Ответ 11

Этот разговор и видео из Herb Sutter объясняет новые коннотации const в отношении безопасности потоков.

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

Ответ 12

Собственно, это не... не полностью, во всяком случае.

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

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

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

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

Ответ 13

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

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

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

Константная переменная Предполагается, что данные, помеченные именем переменной, не могут быть изменены.

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

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

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

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

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

Ответ 14

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

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.