В стандарте C++ говорится, что изменение объекта первоначально объявило const
неопределенное поведение. Но тогда как конструкторы и деструкторы действуют?
class Class {
public:
Class() { Change(); }
~Class() { Change(); }
void Change() { data = 0; }
private:
int data;
};
//later:
const Class object;
//object.Change(); - won't compile
const_cast<Class&>( object ).Change();// compiles, but it's undefined behavior
Я подразумеваю здесь, что конструктор и деструктор делают точно то же самое как код вызова, но им позволяют изменить объект, и вызывающей стороне не разрешают - он сталкивается с неопределенным поведением.
Как это, как предполагается, работает при реализации и согласно стандарту?
Стандарт явно позволяет конструкторам и деструкторам иметь дело с объектами const
. из 12.1 / 4 «Конструкторы»:
Конструктор может быть вызван для объекта
const
,volatile
илиconst volatile
объекта. ... Семантикаconst
иvolatile
(7.1.5.1) не применяется к строящемуся объекту. Такая семантика вступает в силу только после завершения работы конструктора наиболее производного объекта (1.8).
И 12.4/2 «Деструкторы»:
Деструктор может быть вызван для объекта
const
,volatile
илиconst volatile
объекта. ... Семантикаconst
иvolatile
(7.1.5.1) не применяется к уничтожаемому объекту. Такая семантика перестает действовать после запуска деструктора самого производного объекта (1.8).
В качестве фона Страуструп говорит в «Проектировании и развитии C ++» (13.3.2 Уточнение определения const
):
Чтобы гарантировать, что некоторые, но не все,
const
объекты могут быть размещены в постоянном запоминающем устройстве (ПЗУ), я принял правило, согласно которому любой объект, имеющий конструктор (т. Е. Требуемую инициализацию среды выполнения), не может быть помещен в ПЗУ, но другиеconst
объекты могут....
Объявленный объект
const
считается неизменным с момента завершения конструктора до начала его деструктора. Результат записи в объект между этими точками считается неопределенным.При первоначальной разработке
const
я помню, что утверждал, что идеальнымconst
был бы объект, который доступен для записи до тех пор, пока конструктор не запустится, а затем станет доступным только для чтения с помощью некоторой аппаратной магии, и, наконец, при входе в деструктор снова становится доступным для записи. Можно представить себе архитектуру с тегами, которая действительно работает таким образом. Такая реализация вызвала бы ошибку времени выполнения, если бы кто-то мог писать в объект, определенныйconst
.С другой стороны, кто-то может писать в объект, не определенныйconst
, который был передан как ссылка или указательconst
. В обоих случаях пользователю придется сначала отброситьconst
. Смысл этого представления состоит в том, что отбрасываниеconst
для объекта, который был изначально определенconst
, а затем запись в него, в лучшем случае undefined, тогда как то же самое для объекта, который не был ' Первоначально определенный tconst
является допустимым и хорошо определенным.Обратите внимание, что при таком уточнении правил значение
const
не зависит от того, имеет ли тип конструктор или нет; в принципе все делают. Любой объект, объявленныйconst
теперь, может быть помещен в ПЗУ, помещен в сегменты кода, защищен контролем доступа и т. Д., Чтобы гарантировать, что он не изменится после получения своего начального значения. Однако такая защита не требуется, поскольку современные системы не могут защитить каждуюconst
от всех форм коррупции.
Вот способ, которым игнорирование стандарта может привести к неправильному поведению. Рассмотрим ситуацию, подобную этой:
class Value
{
int value;
public:
value(int initial_value = 0)
: value(initial_value)
{
}
void set(int new_value)
{
value = new_value;
}
int get() const
{
return value;
}
}
void cheat(const Value &v);
int doit()
{
const Value v(5);
cheat(v);
return v.get();
}
При оптимизации компилятор знает, что v является константой, поэтому может заменить вызов v.get ()
на 5
.
Но, скажем, в другой единице перевода вы определили cheat ()
следующим образом:
void cheat(const Value &cv)
{
Value &v = const_cast<Value &>(cv);
v.set(v.get() + 2);
}
Таким образом, хотя на большинстве платформ это будет работать, его поведение может измениться в зависимости от того, что делает оптимизатор. .
вот как я его использую:
function customRange(input)
{
var min = new Date();
return {
minDate: ((input.id == "txtStartDate") ? min : (input.id == "txtEndDate" ? $("#txtStartDate").datepicker("getDate") : null)),
maxDate: (input.id == "txtStartDate" ? $("#txtEndDate").datepicker("getDate") : null)
};
}
-121--1041251- Подробнее о том, что сказал Джерри Коффин: стандарт делает доступ к объекту const неопределенным, только если этот доступ происходит в течение жизни объекта.
7,1,5,1/4:
За исключением того, что любой член класса, объявленный изменяемым (7,1,1), может быть изменен, любая попытка изменить объект const в течение его жизни (3,8) приводит к неопределенному поведению.
Время жизни объекта начинается только после завершения конструктора.
3,8/1:
Время жизни объекта типа T начинается, когда получается:
- место хранения с надлежащим выравниванием и размером для типа T, и
- , если T является типом класса с нетривиальным конструктором (12,1), вызов конструктора завершен.
В стандарте не очень много говорится о том, как реализация заставляет это работать, но основная идея довольно проста: const
применяется к объекту, а не (обязательно) к памяти, в которой хранится объект. Поскольку ctor является частью того, что создает объект, он не является объектом до тех пор, пока (через некоторое время после) ctor не вернется. Аналогично, поскольку dtor принимает участие в уничтожении объекта, он также больше не работает с полным объектом.
Константность для определяемого пользователем типа отличается от константности для встроенного типа. Константность при использовании с пользовательскими типами называется "логической константностью". Компилятор следит за тем, чтобы на const-объекте (или указателе, или ссылке) можно было вызывать только функции-члены, объявленные как "const". Компилятор не может выделить объект в некоторой области памяти, доступной только для чтения, потому что non-const функции-члены должны иметь возможность изменять состояние объекта (и даже const функции-члены должны иметь такую возможность, если переменная-член объявлена mutable
).
Для встроенных типов, я полагаю, компилятору разрешено выделять объект в памяти только для чтения (если платформа поддерживает такую возможность). Таким образом, отбрасывание const и модификация переменной может привести к ошибке защиты памяти во время выполнения.