doc*_*doc 6 c++ constructor const-correctness
假设你有一堂课
class C
{
int * i;
public:
C(int * v):i(v) {};
void method() const; //this method does not change i
void method(); //this method changes i
}
Run Code Online (Sandbox Code Playgroud)
现在您可能想要定义此类的const实例
const int * k = whatever;
const C c1(k); //this will fail
Run Code Online (Sandbox Code Playgroud)
但是这会因为非const int C的构造函数C(int*v)而失败
所以你定义了一个const int构造函数
C(const int * v):i(v) {}; //this will fail also
Run Code Online (Sandbox Code Playgroud)
但是这也会失败,因为C的成员"int*i"是非const的.
在这种情况下该怎么办?使用可变吗?铸件?准备const版本的课程?
编辑:经过与Pavel的讨论(下图),我对此问题进行了一些调查.对我来说,C++的作用并不正确.指针目标应该是严格类型,这意味着您不能执行以下操作:
int i;
const int * ptr;
ptr = & i;
Run Code Online (Sandbox Code Playgroud)
在这种情况下,语言语法将const承诺不改变指针的目标.另外int * const ptr是承诺不改变指针值本身.因此,您有两个可以应用const的地方.那么你可能希望你的类为指针建模(为什么不).在这里,事情正在崩溃.C++语法提供了const方法,它们能够保证不会自己更改字段的值,但是没有语法指出你的方法不会改变你的类指针的目标.
解决方法是定义两个类const_C,C例如.然而,这不是一条皇家之路.使用模板,他们的部分专业化很难不陷入混乱.另外像所有可能的参数变化const const_C & arg,const C & arg,const_C & arg,C & arg不漂亮的样子.我真的不知道该怎么办.使用单独的类或const_casts,每种方式似乎都是错误的.
在这两种情况下,我应该标记不将指针的目标修改为const的方法吗?或者只是遵循传统路径,即const方法不会改变对象的状态本身(const方法不关心指针目标).然后在我的情况下,所有方法都是const,因为类正在建模指针,因此指针本身就是T * const.但显然其中一些修改了指针的目标而另一些则没有.
Pav*_*aev 12
听起来你想要一个可以包装的对象int*(然后表现为非const),或者int const*(然后表现为const).单个班级你无法真正做到这一点.
事实上,const应用于你的类的概念应该改变它的语义,就像那样是错误的 - 如果你的类模型指针或迭代器(如果它包装指针,很可能就是这种情况),那么const应该只应用它意味着它不能自己改变,也不应该暗示任何与指出的价值有关的东西.您应该考虑遵循STL对其容器所做的事情 - 这正是为什么它具有不同的类iterator和const_iterator类,两者都是不同的,但前者可以隐式转换为后者.同样,在STL中,const iterator是不一样的const_iterator!所以就这样做吧.
[编辑]这是一种在整个过程中最大限度地重用代码C并且const_C同时确保整体正确性而不是深入研究UB(使用const_cast)的一种棘手的方法:
template<class T, bool IsConst>
struct pointer_to_maybe_const;
template<class T>
struct pointer_to_maybe_const<T, true> { typedef const T* type; };
template<class T>
struct pointer_to_maybe_const<T, false> { typedef T* type; };
template<bool IsConst>
struct C_fields {
typename pointer_to_maybe_const<int, IsConst>::type i;
// repeat for all fields
};
template<class Derived>
class const_C_base {
public:
int method() const { // non-mutating method example
return *self().i;
}
private:
const Derived& self() const { return *static_cast<const Derived*>(this); }
};
template<class Derived>
class C_base : public const_C_base<Derived> {
public:
int method() { // mutating method example
return ++*self().i;
}
private:
Derived& self() { return *static_cast<Derived*>(this); }
};
class const_C : public const_C_base<const_C>, private C_fields<true> {
friend class const_C_base<const_C>;
};
class C : public C_base<C>, private C_fields<false> {
friend class C_base<C>;
};
Run Code Online (Sandbox Code Playgroud)
如果你实际上只有很少的字段,那么在两个类中复制它们可能更容易,而不是用于结构.如果有很多,但它们都是相同的类型,那么直接将该类型作为类型参数传递更简单,而不用const打包器模板.
您的示例不会失败,k是按值传递的.该成员i是"隐式常量",因为C当实例不变时,不能更改直接成员.
Constness说初始化后你不能改变成员,但是当然允许用初始化列表中的值初始化它们 - 你怎么能给它们一个值呢?
什么是行不通的是调用构造函数而不公开它;)
更新解决更新的问题:
是的,C++有时会强迫你进行一些冗长,但const正确性是一种常见的标准行为,你不能在不打破期望的情况下重新定义.Pavels的答案已经解释了一个常见的习惯用法,它用于像STL这样的成熟的库中,用于解决这种情况.
有时你必须接受语言有局限性并仍然处理界面用户的期望,即使这意味着应用一个明显不太理想的解决方案.