组合常量和非常量引用数据成员的单个类

Single class which combines const and nonconst reference data member

本文关键字:常量 单个类 数据成员 非常 组合 引用      更新时间:2023-10-16

这里有一组C++类,它们实现了一种适配器模式:

#include <iostream>
class Cfoo
{
public:
    explicit Cfoo(int i):i_(i){}
    void SetI(int i){ i_ = i; }
    int GetI()const{ return(i_); }
private:
    int i_;
};
class CfooHolderConst
{
public:
    explicit  CfooHolderConst(const Cfoo& foo):foo_(foo){}
    int GetI()const{ return( foo_.GetI() ); }
private:
    const Cfoo& foo_;
};
class CfooHolderNonConst
{
public:
    explicit CfooHolderNonConst(Cfoo& foo):foo_(foo){};
    int GetI()const{ return( foo_.GetI() ); }
    void SetI(int i){ foo_.SetI(i); }
private:
    Cfoo& foo_;
};
int main(  int argc, char* argv[] )
{
    const Cfoo myConstFoo(42);
    CfooHolderConst myConstFooHolder(myConstFoo);
    std::cout << myConstFooHolder.GetI() << std::endl;
    Cfoo myNonConstFoo(1);
    CfooHolderNonConst myNonConstFooHolder(myNonConstFoo);
    myNonConstFooHolder.SetI(42);
    std::cout << myConstFooHolder.GetI() << std::endl;
    return(0);
}

我想将CfooHolderNonConst和CFooHolderConst组合成一个类,否则,从另一个类中继承一个。对Cfoo的引用在这里是一个问题,因为在CFooHolderConst中,它需要定义为const Cfoo&,而在CfooHolderNonConst中,它需要是Cfoo&。

这与此处的interator/cont_iterator问题类似:如何避免实现常量和非常量迭代器的代码重复?

但我希望,因为这不必满足STL迭代器的要求,所以可能会有一个更简单的解决方案。

在过去,我已经解决了这类问题,方法是将const和nonconst指针都作为类成员,并从重载构造函数中设置一个或另一个。这浪费了空间,看起来很笨拙。有没有更优雅的解决方案?

是的,可以做到:

template< typename T > CHolderReader
{
 public: 
    explicit  CHolderBase( T& t):t_(t){}
    int Get()const { return t_.GetI(); }
 protected:
    ~CHolderReader() {}
 protected:
    T& t_;
};
template< typename T > CHolderReaderWriter : public CHolderReader< T >
{
public:
   void Set( int i)
   {
       t_.SetI(i);
   }
};
typedef CHolderReader<const Cfoo> CFooHolderConst;
typedef CHolderReaderWriter<Cfoo> CFooHolderNonConst;    

实际上,这是一个示例,您可以将底层数据的获取封装在其常量或非常量状态中。除非模板化的类型是const,否则Reader会保留一个非const引用,但不允许您对其进行写入,因此当您确实需要对其进行写操作时,可以像使用CHolderReaderWriter一样对其进行扩展。

您可以创建一个模板类,该类将为类的常量和非常量版本提供常量功能,然后继承以扩展具有修改成员功能的非常量版本:

class Cfoo
{
public:
    explicit Cfoo(int i):i_(i){}
    void SetI(int i){ i_ = i; }
    int GetI()const{ return(i_); }
private:
    int i_;
};
class CfooHolderNonConst;
template<class Foo>
class CFooHolder
{
    friend class CfooHolderNonConst;
public:
    explicit  CFooHolder(Foo& foo):foo_(foo){}
    int GetI()const{ return( foo_.GetI() ); }
private:
    Foo& foo_;
};
typedef CFooHolder<const Cfoo> CfooHolderConst;
class CfooHolderNonConst: public CFooHolder<Cfoo>
{
public:
    explicit CfooHolderNonConst(Cfoo& foo):CFooHolder(foo){};
    void SetI(int i){ foo_.SetI(i); }
};

我认为将const和nonconst接口作为单独的类是个好主意。

它们为用户提供不同的接口,并具有不同的语义。在您的示例中,重复也是最小的。

如果你真的想拥有相同的类(提供仍然有意义的语义),那么我认为你想要这样的东西:

const Cfoo f1( 5 );
const CfooHolder h1( f1 );
Cfoo f2( 0 );
CfooHolder h2( f2 );

我认为您希望C++做出以下决定:a) 如果Cfoo对象是const,则将其视为const;如果它是非常数,则将它视为非常数。线索既包括Cfoo的定义,也包括CfooHolder。如果Cfooconst,则CfooHolder必须声明为const,否则它将无法编译。如果Cfoo是非常量,那么您可以创建CfooHolder,它可以是const和非const。b) 方法SetI()在const CfooHolder对象中使用时应停止编译。在上面的例子中,h1.SetI( 6 );不应该编译。

我的答案是,如果a)起作用,那么b)也会自动起作用。问题是实现a),据我所知,这是不可能的。

为了实现这一点,应该在其类的对象的circunstance为const或nonconst的情况下,将属性设置为const或non-const。尽管类的对象可以更改这种"状态",但是属性保持不变。但是,只有当此类的对象是const时(例如,当参数通过常量引用传递时),才能使用const方法。所以,C++不会支持这一点,因为它不是那样工作的。

另一种可能性是让属性本身同时为常量和非常量,这是没有意义的。

简短的回答是:这是不可能的,而且会有代码重复。如果你真的想避免这种情况,并且包装器足够复杂而令人担忧,那么唯一的方法就是创建一个通用保持器,然后在通用保持器周围创建常量和非常量包装器,避免重复到最低限度。

class CfooHolder
{
public:
    explicit CfooHolder(Cfoo& foo):foo_(foo){};
    int GetI()const{ return( foo_.GetI() ); }
    virtual void SetI(int i){ foo_.SetI(i); }
protected:
    Cfoo& foo_;
};
class CfooHolderNonConst : public CfooHolder {
public:
    explicit CfooHolderNonConst(Cfoo& foo):CfooHolder(foo){};
};
class CfooHolderConst: public CfooHolder
{
public:
    explicit  CfooHolderConst(const Cfoo& foo):CfooHolder(const_cast<Cfoo &>( foo )){}
    void SetI(int i){ throw std::runtime_error( "Don't write to me!" ); }
};

它并不完美,但它在规定的条件下工作。方法SetI()会引发运行时错误,但如果CfooHolderConst对象被声明为const,则对SetI()的调用甚至不会编译。

希望这能有所帮助。

为什么不能只使用FooHolder进行非常量(可变)访问,而使用const FooHolder进行常量访问?

您不能在const对象上调用非const限定的方法(如SetI),因此它似乎可以随心所欲。显然,您最初需要从一个非常常量Cfoo创建holder对象。

示例:

class Cfoo
{
public:
    explicit Cfoo(int i) : i_(i) {}
    void SetI(int i) { i_ = i; }
    int GetI() const { return(i_); }
private:
    int i_;
};
class CfooHolder
{
public:
    explicit CfooHolder(Cfoo& foo) : foo_(foo) {};
    void SetI(int i) { foo_.SetI(i); }
    int GetI() const { return( foo_.GetI() ); }
private:
    Cfoo& foo_;
};
void bar(CfooHolder &holder, int i)
{
    holder.SetI(i); // fine
}
void bar(CfooHolder const &constholder, int i)
{
    holder.SetI(i);
    // error: method exists, but I can't call it here
}