【问题标题】:Single class which combines const and nonconst reference data member结合了 const 和 nonconst 引用数据成员的单个类
【发布时间】:2012-03-11 20:08:22
【问题描述】:

这是一组实现了一种适配器模式的 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/const_iterator 类似: How to avoid code duplication implementing const and non-const iterators?

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

过去,我通过将 const 和 nonconst 指针作为类成员,并从重载的构造函数中设置一个或另一个来解决此类问题。这既浪费空间又显得笨拙。有没有更优雅的解决方案?

【问题讨论】:

    标签: c++ const-correctness


    【解决方案1】:

    是的,可以做到:

    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 或 non-const 状态中。 Reader 持有一个非常量引用,除非模板化类型是 const,但不允许您对其进行写入,因此您可以在需要写入时像使用 CHolderReaderWriter 一样对其进行扩展。

    【讨论】:

    • 我的建议是不要保护数据成员,因为它会破坏封装,而是使用友谊。
    • 我不喜欢从基类到派生类。通常我会使用受保护的 get() 来访问底层。如果您真的坚持,您可以使用私有继承和使用将 Get 方法从基类公开。我只是在这里说明如何在这种情况下完成。
    • 这看起来很有希望。我可能会选择你或 AndrzejJ 的解决方案。
    • 对我来说,这不是首先使用继承的典型情况,只是让编译器做我需要的一个技巧。私有继承也可以,但是假设接口可能更复杂并且可能会发生变化,它就会变得更加成问题,因为您需要将所有 const 函数声明为公共的,然后对其进行维护。最重要的是,您必须使该类的 const 版本也私有地继承模板,并再次公开公共接口的所有继承功能。在这种情况下,您有什么特别的理由不使用友谊?
    • 有一些方法可以“密封”一个类,这样你就不能从它继承。 (私有虚拟继承)。如果您虔诚地反对受保护的成员变量,您可以在此处执行此操作。但实际上你是在争论风格。受保护的成员变量对我来说看起来更简单,而且我没有虚拟析构函数或任何虚拟函数的事实表明其他类不应该从我的派生。
    【解决方案2】:

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

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

    【讨论】:

    • 我们显然是在很短的时间间隔内发布的 - 当我发布我的时没有看到你的答案。
    • 我们同时回答了同样的问题。我以前在现实生活中的代码中遇到过这种情况。
    • 我会在这方面做更多的工作,可能会采用您或 CashCow 的解决方案。不幸的是,我不能给你们两个都接受的答案,所以我会选择最接近我的最终代码的人。
    【解决方案3】:

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

    它们为用户提供不同的界面并具有不同的语义。在您的示例中,重复也最少。

    【讨论】:

      【解决方案4】:

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

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

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

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

      为此,属性应设为const 或在其类的对象为常量或非常量的情况下非常量。尽管类的对象可以改变这种“状态”,但是属性保持不变。但是只有当这个类的对象是const(例如,通过常量引用传递参数时)才能使用const方法。所以,C++ 不会支持它,因为它不是那样工作的。

      另一种可能性是让属性本身同时为 const 和 non-const,这是没有意义的。

      简短的回答是:做不到,会有代码重复。如果你真的想避免这种情况,并且包装器足够复杂以至于担心,唯一的方法是创建一个通用持有人,然后围绕通用持有人的常量和非常量包装器,避免重复到最小限度。

      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() 的调用甚至不会编译。

      希望这会有所帮助。

      【讨论】:

        【解决方案5】:

        您不能只使用FooHolder 进行非常量(可变)访问,而使用const FooHolder 进行常量访问,有什么具体原因吗?

        您不能在 const 对象上调用非 const 限定方法(如 SetI),因此它似乎可以满足您的要求。显然,您最初需要从非常量 Cfoo创建持有者对象。

        示例:

        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
        }
        

        【讨论】:

        • “很明显,您最初需要从非常量 Cfoo 创建持有者对象。”那是个问题。我真的需要能够基于 const Cfoo 实例化类。我不太想在那里有一个 const_cast,即使它相当安全。
        • 很难让(编译时)类型系统强制执行您直到运行时才知道的东西。您可以拥有一个抽象基类并在工厂函数中实例化两个派生版本之一,但我认为这违反了您的浪费空间并且看起来笨拙测试。同样,只需将 bool amIConst 保留在持有人中并在任何地方进行检查。
        猜你喜欢
        • 1970-01-01
        • 2020-11-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-06-10
        • 2013-03-08
        • 1970-01-01
        • 2018-12-29
        相关资源
        最近更新 更多