是否有任何替代方法来制作const版本的类?

时间:2014-11-03 11:52:03

标签: c++ const-correctness

在C ++中,我经常面临这样一种情况:我需要编写const和非const版本的类,类似于const_iterator和标准库中的迭代器。

class const_MyClass
{
  public:
      const_MyClass(const int * arr):
         m_arr(arr)
      {
      }

      int method() const;  //does something with m_arr without modifying it

  private:
      const int * m_arr;
}

class MyClass
{
  public:
      MyClass(int * arr):
         m_arr(arr)
      {
      }

      int method() const;  //does something with m_arr without modifying it

      void modify(int i);  //modify m_arr

  private:
      int * m_arr;
}

这个问题是我需要在const_MyClass中重复MyClass的整个代码,并将API中的任何更改分发给这两个类。因此,有时候我会继承const_MyClass并做一些const_casts,这也不是完美的解决方案。仍然当我想通过引用传递const_MyClass实例时,它看起来很蠢:

void func(const const_MyClass & param)

实例参数标有两个" consts",它只有const方法......

这是const构造函数的用武之地,但是有没有现有的替代方案?


有些人用例子来更好地解释问题:

//ok to modify data
void f(int * data)
{
    MyClass my(data);
    my.modify();
    ...
}

//cant modify data, cant use MyClass
void fc(const int * data)
{
    const_MyClass my(data);
    int i = my.method();
    ...
}

4 个答案:

答案 0 :(得分:5)

您可以将模板类作为基础,如下所示:

template<typename T>
class basic_MyClass
{
  public:
      basic_MyClass(T * arr) :m_arr(arr) {}    
      int method() const;  //does something with m_arr without modifying it    
  private:
      T * m_arr;
};

然后,对于你的const版本,因为它没有添加任何东西,你可以使用typedef

typedef basic_MyClass<const int> const_MyClass;

对于非const版本,您可以继承:

class MyClass : public basic_MyClass<int>
{
  public:
    using basic_MyClass::basic_MyClass; // inherit all the constructors
    void modify(int i);  //modify m_arr
};

答案 1 :(得分:1)

在没有可变值的情况下,您是否考虑过跟踪两个指针并从可变操作中引发异常?也许一个例子将有助于描述我的想法。

class MyClass
{
public:
    MyClass(int *mutable_data):
        m_mutable_view(mutable_data), m_readonly_view(mutable_data) 
    {
    }

    MyClass(const int *immutable_data):
        m_mutable_view(NULL), m_readonly_view(immutable_data) 
    {
    }

    int retrieve_value(int index) {
        return m_readonly_view[index];
    }

    void set_value(int index, int value) {
        require_mutable();
        m_mutable_view[index] = value;
    }

protected:
    void require_mutable() {
        throw std::runtime_error("immutable view not available");
    }

private:
    const int *m_readonly_view;
    int *m_mutable_view;
};

这里的想法非常简单 - 使用sentinel值来指示是否可以进行修改,而不是依赖于类型系统来为您执行此操作。就个人而言,我会考虑做@BenjaminLindley建议的inheritance based approach,但我想提出一个可能没有发生过的不同解决方案。

答案 2 :(得分:0)

在与Neil Kirk交谈后,我意识到我做错了什么。我开始将数据与逻辑分开,如他所建议的那样。

此尝试产生了两个类MyClassPtrconst_MyClassPtr。它们只提供数据访问功能(如迭代器),可能看起来像这样:

class const_MyClassPtr
{
  public:
    const_MyClassPtr(const int * arr); 
    int operator [](int i) const;
    const int * ptr() const;

  private:
    const int * m_arr;
}

class MyClassPtr
{
  public:
    MyClassPtr(int * arr);
    int operator [](int i) const;
    int & operator [](int i);
    const int * ptr() const;
    int * ptr();
    //promotion to const pointer
    const_MyClassPtr () const {return const_MyClassPtr(m_arr);}
  private:
    int * m_arr;
}

现在很明显,这些类的对象应该像指针一样对待,所以当我将它们用作函数参数时,我会按值传递它们!

void func(const_MyClassPtr param) //instead of void func(const const_MyClass & param)

提供我创建MyClassOp类模板和使用静态多态的方法。

template <class DERIVED>
class MyClassOp
{
    public:
        const DERIVED & derived() const {return static_cast<const DERIVED &>(*this)}
        DERIVED & derived() {return static_cast<DERIVED &>(*this)}
        int method() const;  //operates on derived() const
        void modify(int i);  //operates on derived()
}

MyClassOp是方法的集合。它没有国家。一般来说,它是trait。为了使这些方法可访问,我重载了->*运算符

class const_MyClassPtr : private MyClassOp<const_MyClassPtr>
{
   public:
    const MyClassOp<MyClassPtr> * operator ->() const {return this;}
    const MyClassOp<MyClassPtr> & operator *() const {return *this;}
    ...
}

class MyClassPtr : private MyClassOp<MyClassPtr>
{
    public:
     MyClassOp<MyClassPtr> * operator ->() {return this;}
     MyClassOp<MyClassPtr> & operator *() {return *this;}
     ...
}

这适用于O.K.,但有点麻烦。如果我有例如相等运算符,我需要编写类似*myptr1 == myptr2的内容来比较两个MyClassPtr对象保存的值(它很容易出错并比较myptr1 == myptr2或期望像*myptr1 == *myptr2这样的东西可以工作)。当我分配类型时:

class MyClass : public MyClassOp<MyClass>
{
   MyClass(int x, int y, int z);
   ...
   int m_arr[3];
}

我希望能够将temporaries用作函数参数。

void f(const_MyClassPtr my);
//use temporary when calling f()
f(MyClass(1, 2, 3));

我可以通过提供转换运算符或转换构造函数(将MyClass转换为const_MyClassPtr)来实现此目的。但是const_MyClassPtr的行为更像是引用而不是指针。如果迭代器是指针的泛化那么为什么一个人不能模仿引用呢?因此,我将MyClassOp分为两部分(const和非const),并将->*实现的const_MyClassPtrMyClassPtr运算符替换为公共继承并更改了名称重新组合参考。我最终得到了以下结构。

MyClassOp : public const_MyClassOp
const_MyClassRef : public const_MyClassOp<const_MyClassRef>
MyClassRef : public MyClassOp<MyClassRef>
MyClass : public MyClassOp<MyClass>

然而const_MyClassRefMyClassRef并不是完美的引用泛化,因为无法模仿某些C ++引用属性,因此Ref后缀表示类似引用的结构。

答案 3 :(得分:-1)

也许你可以在有效的c ++中找到一些提示第4项&#34;避免在const和非const成员函数中重复&#34;

我可能会总结如下(即使使用有点丑陋的演员,也可以避免代码重复):

struct my_class
{
    my_class(int x):_x(x){};

    const int& method(void) const;

    int& method(void);

    int _x;

};

const int& my_class::method(void) const   //func for const instance
{
    return _x;
}


int& my_class::method(void)         //func for normal instance
{
    return const_cast<int& >(static_cast<const my_class& >(*this).method()) ;
}




int main()
{
    my_class a(1);
    const my_class b(2);

    a.method() = 5;
    cout << a.method() << endl;

    //b.method() = 4;   //b is const, wont compile
    cout << b.method() << endl;

    return 0;
}