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

时间:2009-11-09 05:59:44

标签: 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
    }

现在您可能想要定义此类的const实例

    const int * k = whatever;
    const C c1(k); //this will fail

但由于非const int C的构造函数C(int * v)

,这将失败

所以你定义了一个const int构造函数

    C(const int * v):i(v) {}; //this will fail also

但是这也会失败,因为C的成员“int * i”是非const。

在这种情况下该怎么办?使用可变吗?铸件?准备类的const版本?

编辑:在与Pavel讨论之后(下面)我对此问题进行了一些调查。对我来说,C ++的作用并不正确。指针目标应该是严格类型,这意味着您不能执行以下操作:

int i;
const int * ptr;
ptr = & i;

在这种情况下,语言语法将const视为不改变指针目标的承诺。另外int * const ptr是一个不改变指针值本身的承诺。因此,您有两个可以应用const的地方。那么你可能希望你的类为指针建模(为什么不)。在这里,事情正在崩溃。 C ++语法提供了const方法,它们能够保证不会自己更改字段的值,但是没有语法指出你的方法不会改变你的类指针的目标。

解决方法是定义两个类const_CC。然而,这不是一条皇家之路。使用模板,他们的部分专业化很难不陷入混乱。此外,const const_C & argconst C & argconst_C & argC & arg等所有可能的参数变体看起来都不漂亮。我真的不知道该怎么办。使用单独的类或const_casts,每种方式似乎都是错误的。

在这两种情况下,我应该标记不将指针的目标修改为const的方法吗?或者只是遵循传统路径,即const方法不会改变对象的状态本身(const方法不关心指针目标)。然后在我的情况下,所有方法都是const,因为类正在建模指针,因此指针本身是T * const。但很明显,其中一些修改了指针的目标,而另一些则没有。

7 个答案:

答案 0 :(得分:12)

听起来你想要一个可以包装int*(然后表现为非const)或int const*(然后表现为const)的对象。单个班级你无法真正做到这一点。

事实上,const应用于你的类的概念应该改变它的语义就是错误的 - 如果你的类模拟指针或迭代器(如果它包装了一个指针,很可能就是这种情况) ),然后const应用于它应该只意味着它不能自己改变,并且不应该暗示任何关于指向的值。您应该考虑遵循STL对其容器所做的事情 - 这正是为什么它具有不同的iteratorconst_iterator类,两者都是不同的,但前者可以隐式转换为后者。同样,在STL中,const iteratorconst_iterator不同!所以就这样做。

[编辑] 这是在Cconst_C之间最大限度地重复使用代码的一种棘手的方法,同时确保整个过程中的const-correctness,而不是深入研究U.B. (使用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>;
};

如果你实际上只有很少的字段,那么在两个类中复制它们可能更容易,而不是用于结构。如果有很多,但它们都是相同的类型,那么直接将该类型作为类型参数传递更简单,而不是打扰const包装器模板。

答案 1 :(得分:5)

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

不起作用的是调用构造函数而不将其公开;)

更新解决更新的问题:

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

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

答案 2 :(得分:0)

你的问题没有意义。你在哪里得到所有这些“这将失败”的预测?它们都不是真的。

首先,构造函数的参数是否声明为const完全无关紧要。当您按值传递时(如在您的情况下),您可以在任何情况下传递const对象作为参数,无论参数是否声明为const

其次,从构造函数的角度来看,对象是 NOT 常量。无论您正在构造什么类型的对象(常量或不常量),在构造函数内,对象永远不会常量。因此,不需要mutable或任何其他内容。

为什么不尝试编译你的代码(看看什么都不会失败),而不是做出“失败”的奇怪的无根据预测?

答案 3 :(得分:0)

const int *与int * const不同。当你的类是const时,你有后者(指向可变整数的常量指针)。你传递的是前者(指向常数整数的可变指针)。由于显而易见的原因,这两者是不可互换的。

答案 4 :(得分:0)

实例化时

const C c1(...)

因为c1是const,其成员 i 会转到:

int* const i;

正如其他人提到的,这被称为隐式const。

现在,稍后在您的示例中,您尝试传递const int *。所以你的构造函数基本上是这样做的:

const int* whatever = ...;
int* const i = whatever; // error

您收到错误的原因是您无法将const转换为非const。 'whatever'指针不允许更改它指向的东西(int部分是const)。 'i'指针 允许更改它指向的内容,但不能自行更改(指针部分为const)。

您还提到希望您的类为指针建模。 STL使用迭代器完成此操作。一些实现使用的模型是一个名为'const_iterator'的类,它隐藏了真正的指针,只提供const方法来访问指向的数据。然后还有一个'iterator'类继承自'const_iterator',添加了非const重载。这很好用 - 它是一个自定义类,允许与指针相同的constness,其中类型镜像指针,如下所示:

  • iterator - &gt; T *
  • const iterator - &gt; T * const
  • const_iterator - &gt; const T *
  • const const_iterator - &gt; const T * const

希望这是有道理的:)

答案 5 :(得分:0)

好的,这是我到目前为止所做的。为了在没有const_casts的类的const版本或额外的空间开销之后允许继承,我创建了一个基本上看起来像这样的联合:

template <typename T>
union MutatedPtr
{
protected:
    const T * const_ptr;
    T * ptr;

public:
    /**
     * Conversion constructor.
     * @param ptr pointer.
     */
    MutatedPtr(const T * ptr): const_ptr(ptr) {};

    /**
     * Conversion to T *.
     */
    operator T *() {return ptr;}

    /**
     * Conversion to const T *.
     */
    operator const T *() const {return const_ptr;}
};

当声明MutatedPtr字段时,它最终会在const方法中返回const_ptr,而非常量方法则返回plain ptr。它将方法的常量委托给指针目标,这在我的案例中是有意义的。

有任何意见吗?

顺便说一句,你当然可以用非指针类型甚至方法做类似的事情,所以看起来不需要引入mutable关键字(?)

答案 6 :(得分:0)

我遇到了同样不幸的问题after lamenting the lack of a const constructor in C++我得出的结论是,两种模板化是最好的方法,至少在重用方面是这样。

我的案例/解决方案的一个非常简化的版本是:

 template< typename DataPtrT >
 struct BaseImage
 {
     BaseImage( const DataPtrT & data ) : m_data( data ) {}

     DataPtrT getData() { return m_data; } // notice that if DataPtrT is const 
                                           // internally, this will return
                                           // the same const type
     DataPtrT m_data;
 };

 template< typename DataPtrT >
 struct DerivedImage : public BaseImage<DataPtrT>
 {
 };

有一个非常不幸的类继承丢失,但在我的情况下,可以使一种类型的转换操作符能够在const和非const类型之间进行转换,并且明确知道如何在转换下进行转换。引擎盖。与复制构造函数和/或重载的解引用运算符的某些适当使用相混合可能会使您到达您想要的位置。

 template< typename OutTypeT, typename inTypeT )
 image_cast< shared_ptr<OutTypeT> >( const shared_ptr<InTypeT> & inImage )
 {
     return shared_ptr<OutTypeT>( new OutTypeT( inImage->getData() ) );
 }