Рассмотрим следующее:
int i = 3;
i
- это объект, который имеет тип int
. Он не квалифицирован как cv (не является const
или volatile
, или обоими).
Теперь добавим:
const int& j = i;
const int* k = &i;
j
является ссылкой, которая ссылается на i
, а k
является указателем, который указывает на i
. (Отныне мы просто объединяем слова «ссылаются на» и «указывает на» и просто «указывают на».)
На данный момент у нас есть две cv-квалифицированные переменные j
и k
, которые указывают на неквалифицированный cv объект. Это упомянуто в §7.1. 5.1 / 3:
Указатель или ссылка на тип с квалификацией cv не обязательно должен указывать или ссылаться на объект с квалификацией cv, но он обрабатывается так, как если бы он был; квалифицированный константный путь доступа не может использоваться для изменения объекта, даже если упомянутый объект является неконстантным объектом и может быть изменен через какой-либо другой путь доступа. [Примечание: cv-квалификаторы поддерживаются системой типов, поэтому они не могут быть преобразованы без приведения (5.2.11). ]
Это означает, что компилятор должен учитывать, что j
и k
являются cv-квалифицированными, даже если они указывают на неквалифицированный cv объект. (Таким образом, j = 5
и *k = 5
являются незаконными, хотя i = 5
является законным.)
Теперь рассмотрим удаление const
из этих:
const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
Это законно (см. П. 5.2.11), но разве это неопределенное поведение? № См. §7.1. 5.1 / 4:
За исключением того, что любой член класса, объявленный mutable (7.1.1), может быть изменен, любая попытка изменить объект const в течение срока его службы (3.8) приводит к неопределенному поведению .
Акцент мой.
Помните, что i
- это , а не const
, и что j
и k
оба указывают на i
. Все, что мы сделали, это сказали системе типов удалить константный квалификатор из типа, чтобы мы могли изменить указанный объект, а затем изменить i
через эти переменные.
Это точно так же, как и:
int& j = i; // removed const with const_cast...
int* k = &i; // ..trivially legal code
j = 5;
*k = 5;
И это тривиально законно. Теперь мы считаем, что i
было таким:
const int i = 3;
Что из нашего кода сейчас?
const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
Теперь это приводит к неопределенному поведению , потому что i
является объектом с константой. Мы сказали системе типов удалить const
, чтобы мы могли изменить указанный объект , а затем изменили объект с квалификацией const . Это не определено, как указано выше.
Опять более очевидно как:
int& j = i; // removed const with const_cast...
int* k = &i; // ...but this is not legal!
j = 5;
*k = 5;
Обратите внимание, что просто делать это:
const_cast<int&>(j);
*const_cast<int*>(k);
Совершенно законно и определено, поскольку никакие const-квалифицированные объекты не изменяются; мы просто возимся с системой типов.
Теперь рассмотрим:
struct foo
{
foo() :
me(this), self(*this), i(3)
{}
void bar() const
{
me->i = 5;
self.i = 5;
}
foo* me;
foo& self;
int i;
};
Что const
на bar
делает с участниками? Это позволяет получить доступ к ним через то, что называется cv-квалифицированным путем доступа . (Это делается путем изменения типа this
с T* const
на cv T const*
, где cv
- это cv-квалификаторы функции.)
Так, какие типы членов во время выполнения bar
? Это:
// const-pointer-to-non-const, where the pointer points cannot be changed
foo* const me;
// foo& const is ill-formed, cv-qualifiers do nothing to reference types
foo& self;
// same as const int
int const i;
Конечно, типы не имеют значения, поскольку важна константная квалификация , указывающая на объекты, а не указатели. (Если бы k
выше было const int* const
, последнее const
не имеет значения.) Теперь мы рассмотрим:
int main()
{
foo f;
f.bar(); // UB?
}
В пределах bar
оба значения me
и self
указывают на неконстантное значение foo
, поэтому, как и в случае с int i
, мы имеем четко определенное поведение. Если бы у нас было:
const foo f;
f.bar(); // UB!
У нас был бы UB, как и с const int
, потому что мы бы модифицировали объект с квалификацией const.
В вашем вопросе у вас нет константных объектов, поэтому у вас нет неопределенного поведения.
И просто добавьте обращение к авторитету, рассмотрим уловку const_cast
Скотта Мейерса, использованную для утилизации функции, удовлетворяющей const, в неконстантной функции:
struct foo
{
const int& bar() const
{
int* result = /* complicated process to get the resulting int */
return *result;
}
int& bar()
{
// we wouldn't like to copy-paste a complicated process, what can we do?
}
};
Он предлагает:
int& bar(void)
{
const foo& self = *this; // add const
const int& result = self.bar(); // call const version
return const_cast<int&>(result); // take off const
}
Или как обычно написано:
int& bar(void)
{
return const_cast<int&>( // (3) remove const from result
static_cast<const foo&>(*this) // (1) add const to this
.bar() // (2) call const version
);
}
NЭто опять-таки совершенно законно и четко определено. В частности, поскольку эта функция должна вызываться для неконстантной foo
, мы совершенно безопасны при удалении константной квалификации из возвращаемого типа int& boo() const
.
(Если кто-то не стреляет в себя с помощью звонка const_cast
+.)
Подведем итог:
struct foo
{
foo(void) :
i(),
self(*this), me(this),
self_2(*this), me_2(this)
{}
const int& bar() const
{
return i; // always well-formed, always defined
}
int& bar() const
{
// always well-formed, always well-defined
return const_cast<int&>(
static_cast<const foo&>(*this).
bar()
);
}
void baz() const
{
// always ill-formed, i is a const int in baz
i = 5;
// always ill-formed, me is a foo* const in baz
me = 0;
// always ill-formed, me_2 is a const foo* const in baz
me_2 = 0;
// always well-formed, defined if the foo pointed to is non-const
self.i = 5;
me->i = 5;
// always ill-formed, type points to a const (though the object it
// points to may or may not necessarily be const-qualified)
self_2.i = 5;
me_2->i = 5;
// always well-formed, always defined, nothing being modified
// (note: if the result/member was not an int and was a user-defined
// type, if it had its copy-constructor and/or operator= parameter
// as T& instead of const T&, like auto_ptr for example, this would
// be defined if the foo self_2/me_2 points to was non-const
int r = const_cast<foo&>(self_2).i;
r = const_cast<foo* const>(me_2)->i;
// always well-formed, always defined, nothing being modified.
// (same idea behind the non-const bar, only const qualifications
// are being changed, not any objects.)
const_cast<foo&>(self_2);
const_cast<foo* const>(me_2);
// always well-formed, defined if the foo pointed to is non-const
// (note, equivalent to using self and me)
const_cast<foo&>(self_2).i = 5;
const_cast<foo* const>(me_2)->i = 5;
// always well-formed, defined if the foo pointed to is non-const
const_cast<foo&>(*this).i = 5;
const_cast<foo* const>(this)->i = 5;
}
int i;
foo& self;
foo* me;
const foo& self_2;
const foo* me_2;
};
int main()
{
int i = 0;
{
// always well-formed, always defined
int& x = i;
int* y = &i;
const int& z = i;
const int* w = &i;
// always well-formed, always defined
// (note, same as using x and y)
const_cast<int&>(z) = 5;
const_cast<int*>(w) = 5;
}
const int j = 0;
{
// never well-formed, strips cv-qualifications without a cast
int& x = j;
int* y = &j;
// always well-formed, always defined
const int& z = i;
const int* w = &i;
// always well-formed, never defined
// (note, same as using x and y, but those were ill-formed)
const_cast<int&>(z) = 5;
const_cast<int*>(w) = 5;
}
foo x;
x.bar(); // calls non-const, well-formed, always defined
x.bar() = 5; // calls non-const, which calls const, removes const from
// result, and modifies which is defined because the object
// pointed to by the returned reference is non-const,
// because x is non-const.
x.baz(); // well-formed, always defined
const foo y;
y.bar(); // calls const, well-formed, always defined
const_cast<foo&>(y).bar(); // calls non-const, well-formed,
// always defined (nothing being modified)
const_cast<foo&>(y).bar() = 5; // calls non-const, which calls const,
// removes const from result, and
// modifies which is undefined because
// the object pointed to by the returned
// reference is const, because y is const.
y.baz(); // well-formed, always undefined
}
Я имею в виду стандарт ISO C ++ 03.