Ash*_*ain 9 c++ const const-correctness mutable
在我正在编写的程序中出现了以下模式.我希望它不是太做作,但它设法Foo
在const方法中改变一个对象Foo::Questionable() const
,而不使用任何const_cast或类似的东西.基本上,Foo
存储参考FooOwner
,反之亦然,并在Questionable()
,Foo
设法修改本身在一个const方法通过调用mutate_foo()
关于它的主人.问题遵循代码.
#include "stdafx.h"
#include <iostream>
using namespace std;
class FooOwner;
class Foo {
FooOwner& owner;
int data;
public:
Foo(FooOwner& owner_, int data_)
: owner(owner_),
data(data_)
{
}
void SetData(int data_)
{
data = data_;
}
int Questionable() const; // defined after FooOwner
};
class FooOwner {
Foo* pFoo;
public:
FooOwner()
: pFoo(NULL)
{}
void own(Foo& foo)
{
pFoo = &foo;
}
void mutate_foo()
{
if (pFoo != NULL)
pFoo->SetData(0);
}
};
int Foo::Questionable() const
{
owner.mutate_foo(); // point of interest
return data;
}
int main()
{
FooOwner foo_owner;
Foo foo(foo_owner, 0); // foo keeps reference to foo_owner
foo_owner.own(foo); // foo_owner keeps pointer to foo
cout << foo.Questionable() << endl; // correct?
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这是定义的行为吗?应该Foo::data
声明可变吗?或者这是一个标志我做的事情是致命的错误?我正在尝试实现一种懒惰初始化的'数据',它只在被请求时设置,并且下面的代码编译好没有警告,所以我有点紧张我在UB土地.
编辑:const
on Questionable()只使直接成员为const,而不是对象指向或引用的对象.这会使代码合法吗?我对以下事实感到困惑:in Questionable()
,this
has the type const Foo*
,以及调用堆栈的下游,FooOwner
合法地有一个用于修改的非const指针Foo
.这是否意味着Foo
可以修改对象?
编辑2:也许是一个更简单的例子:
class X {
X* nonconst_this; // Only turns in to X* const in a const method!
int data;
public:
X()
: nonconst_this(this),
data(0)
{
}
int GetData() const
{
nonconst_this->data = 5; // legal??
return data;
}
};
Run Code Online (Sandbox Code Playgroud)
GMa*_*ckG 25
考虑以下:
int i = 3;
Run Code Online (Sandbox Code Playgroud)
i
是一个对象,它有类型int
.它不是CV-合格的(不是const
或volatile
,或两者兼而有之.)
现在我们添加:
const int& j = i;
const int* k = &i;
Run Code Online (Sandbox Code Playgroud)
j
是指向的参考i
,并且k
是指向的指针i
.(从现在开始,我们只需将"引用"和"指向"组合成"指向".)
此时,我们有两个cv限定的变量,j
并k
指向一个非cv限定的对象.这在§7.1.5.1/ 3中提到:
对cv限定类型的指针或引用不需要实际指向或引用cv限定对象,但它被视为具有; 即使引用的对象是非const对象并且可以通过某些其他访问路径进行修改,也不能使用const限定的访问路径来修改对象.[注意:类型系统支持cv-qualifiers,因此在不进行强制转换的情况下不能破坏它们(5.2.11).]
这意味着编译器必须尊重它j
并且k
是cv限定的,即使它们指向非cv限定的对象.(所以j = 5
并且*k = 5
非法,即使i = 5
是合法的.)
我们现在考虑删除const
那些:
const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
Run Code Online (Sandbox Code Playgroud)
这是合法的(§参见5.2.11),但它是不确定的行为?号见§7.15.1/4:
除了可以修改声明为mutable(7.1.1)的任何类成员之外,任何在其生命周期内修改const对象的尝试(3.8)都会导致未定义的行为. 强调我的.
请记住,i
是不是 const
和j
和k
两个点i
.我们所做的就是告诉类型系统从类型中删除const限定符,这样我们就可以修改指向的对象,然后i
通过这些变量进行修改.
这跟做的完全一样:
int& j = i; // removed const with const_cast...
int* k = &i; // ..trivially legal code
j = 5;
*k = 5;
Run Code Online (Sandbox Code Playgroud)
这是非常合法的.我们现在考虑的i
是:
const int i = 3;
Run Code Online (Sandbox Code Playgroud)
我们现在的代码是什么?
const_cast<int&>(j) = 5;
*const_cast<int*>(k) = 5;
Run Code Online (Sandbox Code Playgroud)
它现在导致未定义的行为,因为它i
是一个const限定的对象.我们告诉类型系统删除const
所以我们可以修改指向的对象,然后修改一个const限定的对象.如上所述,这是未定义的.
再次,更明显的是:
int& j = i; // removed const with const_cast...
int* k = &i; // ...but this is not legal!
j = 5;
*k = 5;
Run Code Online (Sandbox Code Playgroud)
请注意,只需这样做:
const_cast<int&>(j);
*const_cast<int*>(k);
Run Code Online (Sandbox Code Playgroud)
完全合法和定义,因为没有修改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;
};
Run Code Online (Sandbox Code Playgroud)
是什么const
在bar
做的成员?它使得访问它们通过称为cv限定访问路径的东西.(它通过更改this
from 的类型来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;
Run Code Online (Sandbox Code Playgroud)
当然,类型是无关紧要的,因为重要的是指向对象的const限定,而不是指针.(k
如上所述const int* const
,后者const
无关紧要.)我们现在考虑:
int main()
{
foo f;
f.bar(); // UB?
}
Run Code Online (Sandbox Code Playgroud)
内bar
,双方me
并self
指向一个非const foo
,所以只是想int i
上面我们已经明确的行为.我们有没有:
const foo f;
f.bar(); // UB!
Run Code Online (Sandbox Code Playgroud)
我们本来就有UB,const int
因为我们将修改一个const限定对象.
在您的问题中,您没有const限定对象,因此您没有未定义的行为.
只是为了增加对权威的吸引力,考虑const_cast
Scott Meyers 的技巧,用于在非const函数中回收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?
}
};
Run Code Online (Sandbox Code Playgroud)
他建议:
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
}
Run Code Online (Sandbox Code Playgroud)
或者通常如何写:
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
);
}
Run Code Online (Sandbox Code Playgroud)
请注意,这又是完全合法且定义明确的.具体来说,因为必须在非const限定的情况下调用此函数,所以foo
我们完全可以从返回类型中剥离const限定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
}
Run Code Online (Sandbox Code Playgroud)
我指的是ISO C++ 03标准.
IMO,你没有做任何技术错误.可能会更容易理解成员是否是指针.
class X
{
Y* m_ptr;
void foo() const {
m_ptr = NULL; //illegal
*m_ptr = 42; //legal
}
};
Run Code Online (Sandbox Code Playgroud)
const
使指针成为 const,而不是指针.
考虑以下区别:
const X* ptr;
X* const ptr; //this is what happens in const member functions
Run Code Online (Sandbox Code Playgroud)
至于引用,因为它们无论如何都不能重新设置,const
方法上的关键字对引用成员没有任何影响.
在你的例子中,我没有看到任何const对象,所以你没有做任何坏事,只是利用const正确性在C++中工作的方式的一个奇怪的漏洞.