如何处理const对象中非const引用成员的初始化?

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对其容器所做的事情 - 这正是为什么它具有不同的类iteratorconst_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打包器模板.


Geo*_*che 5

您的示例不会失败,k是按值传递的.该成员i是"隐式常量",因为C当实例不变时,不能更改直接成员.
Constness说初始化后你不能改变成员,但是当然允许用初始化列表中的值初始化它们 - 你怎么能给它们一个值呢?

什么是行不通的是调用构造函数而不公开它;)

更新解决更新的问题:

是的,C++有时会强迫你进行一些冗长,但const正确性是一种常见的标准行为,你不能在不打破期望的情况下重新定义.Pavels的答案已经解释了一个常见的习惯用法,它用于像STL这样的成熟的库中,用于解决这种情况.

有时你必须接受语言有局限性并仍然处理界面用户的期望,即使这意味着应用一个明显不太理想的解决方案.