【问题标题】:C++ static member modifiers for subclasses子类的 C++ 静态成员修饰符
【发布时间】:2017-03-29 16:01:04
【问题描述】:

考虑以下代码:

#include <stdio.h>
#include <iostream>

/// Header-file
class Base {
public:
  virtual void do_something() const =0;

  int GetAttrib () const {return constattribute_;};

  static const int constattribute_;
};

typedef Base* Derived_Ptr; //<< adress derived classes by their base-class ptr; so no templates for Base

class DerivedA : public Base {
//   static const int constattribute_; //<< change this static attribute for all DerivedA class instances and their derivatives

  void do_something() const {};
};

class DerivedB : public Base {
//   static const int constattribute_; //<< change this static attribute for all DerivedB class instances and their derivatives

  void do_something() const {};
};

/// CC-file
using namespace std;

const int Base::constattribute_(0);
const int DerivedA::constattribute_(1); //<<error: no such variable 'constattribute' in class DerivedA
const int DerivedB::constattribute_(2); //<<error: no such variable 'constattribute' in class DerivedB


int main(void) {
  Derived_Ptr derivedA = new DerivedA();
  Derived_Ptr derivedB = new DerivedB();

  cout << derivedA->GetAttrib() << derivedB->GetAttrib() <<endl;
  return 0;
};

我的意图是我有一些抽象接口(Base),它还定义了一个变量,它应该存在于所有派生类中,并且是可检索的。所有类型的子类都应该被强制/能够重新定义它们的特定值,最好是在类声明期间(毕竟在声明类时这些值是已知的)。

我想实现代码,而不是更改 main() 程序,以便输出为“12”而不是现在(取消注释代码中的当前行)“00”(这样做会隐藏基类中的字段)。

我试图调查此事,有不同的解决方案路径,但其中许多与我的直觉相反: 1. 有些遵循 CRTP 模式,但是如果我想通过 main 中的 base-ptr 来处理我的子类,这是不可能的。 2.其他方案需要为每个派生实例虚拟化'GetAttrib()'函数,这很麻烦,并且修改属性的动作被隐藏在函数定义中。 3. 第三种可能性是删除静态模式并将“constattribute_”字段作为常规成员,但这迫使我将其作为参数拖过所有构造函数。

我很确定必须有一些更聪明的方法来做到这一点。任何提示表示赞赏。

【问题讨论】:

    标签: c++ inheritance static field


    【解决方案1】:

    假设您不必通过 Base* 访问 GetAttr() 并且可以在 Base 本身没有 constattribute_ 的情况下离开,使用 CRTP 可能会得到您想要的。只需遵循可以通过输入另一个间接级别来解决每个编程问题的规则,我在下面做了:

    class Base {
      public:
        virtual void do_something() const = 0;
    
        virtual ~Base()     // should define it as you are using Base*
        {
        }
    };
    
    typedef Base* Derived_Ptr;
    
    template<class T>
    class BaseConstAttr : public Base
    {
      public:
        int GetAttrib () const
        {
          return(constattribute_);
        };
    
        static const int constattribute_;
    };
    
    class DerivedA : public BaseConstAttr<DerivedA>
    {
      public:
        void do_something() const
        {
        };
    };
    
    class DerivedB : public BaseConstAttr<DerivedB>
    {
      public:
        void do_something() const
        {
        };
    };
    
    template<> const int BaseConstAttr<DerivedA>::constattribute_(1);
    template<> const int BaseConstAttr<DerivedB>::constattribute_(2);
    

    如果您需要从继承树的顶部到底部的 GettAttr,您可以稍微修改上面的代码,但这会花费您将 GetAttr 设为虚拟(但仍然只有一个实现):

    class Base {
      public:
        virtual void do_something() const = 0;
        virtual int GetAttrib () const = 0;
    
        virtual ~Base()     // should define it as you are using Base*
        {
        }
    };
    
    typedef Base* Derived_Ptr;
    
    template<class T>
    class BaseConstAttr : public Base
    {
      public:
        int GetAttrib () const
        {
          return(constattribute_);
        };
    
        static const int constattribute_;
    };
    
    class DerivedA : public BaseConstAttr<DerivedA>
    {
      public:
        void do_something() const
        {
        };
    };
    
    class DerivedB : public BaseConstAttr<DerivedB>
    {
      public:
        void do_something() const
        {
        };
    };
    
    template<> const int BaseConstAttr<DerivedA>::constattribute_(1);
    template<> const int BaseConstAttr<DerivedB>::constattribute_(2);
    

    请注意,我不知道深度继承树(即从 DerivedA 和/或 DerivedB 继承时)的表现如何(或糟糕)。在这种情况下,我可能会从 Base 正下方的继承树中删除 BaseConstAttr,并尝试在大多数派生类及其前身之间注入它或使用多重继承。

    【讨论】:

    • 有趣的方法。请注意,您可以在模板参数 (template &lt;int A&gt; class BaseConstAttr ...) 中传递属性值,而不是为每种类型专门指定 BaseConstAttr 并使用 CRTP,然后您有一个 template&lt;int A&gt; const int BaseConstAttr&lt;A&gt;::constattribute_{A};。那么DerivedA 可以继承BaseConstAttr&lt;1&gt;DerivedB 可以继承BaseConstAttr&lt;2&gt;,这就是你需要做的所有事情!
    • 这正是我想要的,谢谢!旁注:尽管我了解编译器需要如何解析静态变量的路径,但这使得在定义基类接口之后有必要实现 CRTP 模式。因此,对于每个子类,我的派生树都会更深一层,其中class BaseConstAttrib 只是稍微扩展了class Base 的接口,我认为可以在声明和定义Base 时实现这一点(我不打算偏离任何子类中如此概述的接口)。
    • 通过这个进一步工作:因为现在 DerivedA 和 DerivedB 直接从 BaseConstAttrib>>Base 继承,您将不得不重新广播 Base 的所有自定义构造函数(它们将有效地我猜是内联的)。这是对从单独的BaseConstAttrib 虚拟继承的权衡取舍,虚拟继承自 Base 并在每个实现子类上派生它(需要记住包含这个单独的接口组件)。
    • 按照 cdhowie 的建议在 int A 上做模板可以有额外的好处,如果静态 const constattrib_ 的唯一目的是在 GetAttr 方法中使用,则可以完全删除它,然后可以直接引用模板参数。构造函数可以在继承树中的任何点通过使用指定基类的附加模板参数来导入。这有一个缺点,即所有这些都是公共的、私有的或受保护的。
    【解决方案2】:

    您请求的内容需要虚拟调度某处,因为直到运行时您才知道正在处理的对象的类型。虚拟调度的目的是准确解决您面临的问题。

    最简单的解决方案是您给出的数字 2:使 GetAttrib() 虚拟,并在您引入阴影 constattribute_ 的每个派生类上实现它。

    【讨论】:

      【解决方案3】:

      基类中的静态变量是单个实例,因此它将在派生类中反映相同。

      您可以在派生类中使用您想要的特定不同值创建相同的静态成员变量。现在将基类中静态变量的getter成员函数设为虚拟,并在派生类中重载它,返回的是静态实例值。

      我已经更新了你的代码来运行它,请检查..

      #include <iostream>
      
      using namespace std;
      
      class Base {
      public:
        static const int constattribute_;
        virtual void do_something() const =0;
        virtual int GetAttrib () const {return constattribute_;};
      };
      
      typedef Base* Derived_Ptr; //<< adress derived classes by their base-class ptr; so no templates for Base
      
      class DerivedA : public Base {
        static const int constattribute_; //<< change this static attribute for all DerivedA class instances and their derivatives
      
        void do_something() const {};
        int GetAttrib () const {return constattribute_;};
      };
      
      class DerivedB : public Base {
        static const int constattribute_; //<< change this static attribute for all DerivedB class instances and their derivatives
      
        void do_something() const {};
        int GetAttrib () const {return constattribute_;};
      };
      
      const int Base::constattribute_(0);
      const int DerivedA::constattribute_(1); //<<error: no such variable 'constattribute' in class DerivedA
      const int DerivedB::constattribute_(2); //<<error: no such variable 'constattribute' in class DerivedB
      
      
      int main(void) {
        Derived_Ptr derivedA = new DerivedA();
        Derived_Ptr derivedB = new DerivedB();
      
        cout << derivedA->GetAttrib() << derivedB->GetAttrib() <<endl;
      
        return 0;
      };
      

      你应该得到想要的输出。

      注意:请记住派生类中的所有成员变量和函数都是私有的。

      【讨论】:

      • 您的回答是有效的,我将获得所需的输出,并概述了我提出的解决方案路径 (2)。然而,代价是在每个子类中大量重新实现GetAttrib()。按照您的解决方案,原则上我还可以将每个子类中的 localshadowing constattribute_variable 作为普通返回值移动到其函数定义中。正如我所说,这会将类的真实行为隐藏为实现细节,而不是将其作为子类本身的静态属性。
      猜你喜欢
      • 1970-01-01
      • 2014-01-02
      • 1970-01-01
      • 2017-09-06
      • 2011-09-29
      • 2018-10-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多