有没有其他方法可以制作 const 版本的类?

Posted

技术标签:

【中文标题】有没有其他方法可以制作 const 版本的类?【英文标题】:Are there any alternatives to making const version of class? 【发布时间】:2014-11-03 11:52:02 【问题描述】:

在 C++ 中,我经常需要准备 const 和 non-const 版本的类,类似于标准库中的 const_iterator 和 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;

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

void func(const const_MyClass & param)

Instance param 标有两个“const”,而且它只有 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();
    ...

【问题讨论】:

为什么你不能只拥有普通类 MyClassconst 实例? 为什么不制作methodmodify 以数组为参数的普通函数? const_iterator 和 iterator 并不直接操作数据,而是将数据传递给其他代码进行操作。如果你的类真的类似于迭代器,你应该做类似的事情。 我的说法有争议怎么办?运算符不会更改数据,而是将其传递给其他代码进行修改。我只是根据您提供的代码提出建议。 一个数据结构是通用的,它不知道如何使用它的数据。操作数据的逻辑应该在别处。它也可以在一个类中,但不能在迭代器类中。有了更多关于你在做什么的背景信息,我可以提出更具体的建议。 【参考方案1】:

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

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;

对于你的非常量版本,你可以继承:

class MyClass : public basic_MyClass<int>

  public:
    using basic_MyClass::basic_MyClass; // inherit all the constructors
    void modify(int i);  //modify m_arr
;

【讨论】:

我正在考虑类似的事情,除了想象T m_arr; 而不是T * m_arr;。在这种情况下,你传递指针,就像在basic_MyClass&lt;int *&gt; 中一样,但你也可以传递具有指针语义的分配类型basic_MyClass&lt;MyArray&gt;(并且 MyArray 可以自己分配空间)。出色的灵活性,但可能无法避免复杂性。 @doc:对不起。我不明白你想说什么。 @doc:实际上,也许我知道。起初,我以为您是在评论我的解决方案的潜在问题或困难。但是现在我认为您只是在尝试想象更多有趣的可能性(对于其他潜在问题,不一定是您的问题),对吗?【参考方案2】:

您是否考虑过在没有可变值可用时简单地跟踪两个指针并从可变操作中引发异常?也许一个例子可以帮助描述我的想法。

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;
;

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

【讨论】:

感谢您的回答。信不信由你,但我也在考虑类似的事情(只是我使用联合来存储指针和布尔变量)。我对“额外”变量和额外检查的需要并不满意,只是为了保持常量正确。我已经为我自己的问题提供了答案:/如果你愿意,可以看看。【参考方案3】:

在与 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。为了使这些方法可以访问,我重载了 -&gt;* 运算符

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;
     ...

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

class MyClass : public MyClassOp<MyClass>

   MyClass(int x, int y, int z);
   ...
   int m_arr[3];

我希望能够使用临时变量作为函数参数。

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

我可以通过提供转换运算符或转换构造函数(将MyClass 转换为const_MyClassPtr)来做到这一点。但是const_MyClassPtr 的行为更像是引用而不是指针。如果迭代器是指针的泛化,那么为什么不能模仿引用呢?因此,我将MyClassOp 分为两部分(常量和非常量),并将const_MyClassPtrMyClassPtr 实现的-&gt;* 运算符替换为公共继承,并将它们的名称更改为类似引用。我最终得到了以下结构。

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

但是const_MyClassRefMyClassRef 并不是引用的完美概括,因为它无法模仿某些C++ 引用属性,所以Ref 后缀是用来表示类似引用的结构。

【讨论】:

【参考方案4】:

也许你可以在effective c++ item 4中找到一些提示“Avoid duplication in const and non-const Member function”

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

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;

【讨论】:

也许你能写出它对你有什么帮助。 很遗憾我没有这本书。你能提供这些提示吗? 我认为您误解了这个问题。 What when you have pointer or array in object?

以上是关于有没有其他方法可以制作 const 版本的类?的主要内容,如果未能解决你的问题,请参考以下文章

只能被自己的类或其他子类调用的Java方法

有啥方法可以使变量类型与它所在的类的类型相同,并且是静态的和 const 而不是指针?

如何利用指针改变const类型的值

在java中,有main方法的类叫啥类,没有main方法的类叫啥类

为啥 PHP 不允许私有 const?

从其他类Objective-C访问方法