【问题标题】:Access child members within parent class, C++访问父类中的子成员,C++
【发布时间】:2011-11-03 16:37:43
【问题描述】:

我面临需要访问父类中的子成员变量的情况。我知道这违反了 OO 原则,但我必须处理这样一种情况,即数百个类从一个类继承,并且其中一半停止使用父变量之一,并声明并使用自己的(需要切换从 int 到 int[] 并且显然这样做的人没有考虑在父类中应用此更改)。

一种选择是使用虚拟函数来处理它,但这意味着我必须更改数百个文件/对象中的代码并测试它们中的每一个。因此,我认为如果可以使用一些老式的 C 指针魔法来访问父方法中的这些变量,这将消除对数百个虚拟函数的需求。

基本上这就是我想要实现的目标:

class Parent
{
    void DoSomething()
    {
        // This is what I need
        childMember = 0;
    }
}

class Child1 : Parent
{
    int childMember;
}

class Child2 : Parent
{
    int childMember;
}

如果这可能,请告诉我。如果是,我该如何实现。
欢迎其他建议,但请记住,我只想在父类中进行更改。
TIA。

【问题讨论】:

  • 我已经修改了你的伪代码来证明有多个子类,而不仅仅是一个。希望没问题。
  • 我删除了“c”标签,这不是 C 题。
  • 往上面扔更多的脏东西不会让问题消失,你应该努力重构和收拾烂摊子。
  • 所以你的意思是你所有的孩子班级都有那个成员?但是有不同的类型?

标签: c++ inheritance


【解决方案1】:

如果允许您更改子类的源代码,您可以这样做:

class Parent
{
public:
    void DoSomething()
    {
        getMember() = 0;
    }
    virtual int & getMember() = 0;
};

class Child1 : public Parent
{
    int childMember;
public:
    int & getMember()
    {
        return childMember;
    }
};

class Child2 : public Parent
{
    int childMember;
public:
    int & getMember()
    {
        return childMember;
    }
};

否则,如果你的对象有虚拟表(至少一个虚拟方法),你可以结合C++11 typeid使用static_cast(),因为它比dynamic_cast快三倍左右:

#include <typeinfo>

class Parent
{
public:
    virtual void DoSomething();
};

class Child1 : public Parent
{
public:
    int childMember;
};

class Child2 : public Parent
{
public:
    int childMember;
};

void Parent::DoSomething()
{
    if (typeid(Child1) == typeid(*this))
    {
        auto child = static_cast<Child1*>(this);
        child->childMember = 0;
    }
    else if (typeid(Child2) == typeid(*this))
    {
        auto child = static_cast<Child2*>(this);
        child->childMember = 0;
    }
};

【讨论】:

    【解决方案2】:

    我没有得到静态演员表的反对票。以下作品:

    #include <stdio.h>
    class B;
    class A {
        public:
            A();
            void print();
        private:
            B *child;
    };
    
    class B : public A {
        friend class A;
        public:
            B();
        private:
            int value;
    };
    
    A::A(){
        child = static_cast<B*>(this);
    }
    
    void A::print(){
        printf("value = %d\n", child->value);
    }
    
    B::B(){
        value = 10;
    }
    
    int main(){
        B b;
        b.A::print();
    }
    

    只需确保将 A 函数的声明放在 B 类的定义之后。您可以区分子类,因为如果您找到了正确的子类,静态转换将返回非 NULL。我发现这种方法也很有用,因为 Child 类 (B) 几乎没有改变。

    【讨论】:

      【解决方案3】:

      CRTP 可能在这里有帮助:

      struct Parent
      {
          virtual void DoSomething() = 0;
      };
      
      template <typename Derived>
      struct ParentProxy : Parent
      {
          virtual void DoSomething()
          {
              Derived* p = dynamic_cast<Derived*>(this);
              p->childMember = 27;
          }
      };
      
      struct Child1 : ParentProxy<Child1>
      {
          int childMember;
      };
      
      struct Child2 : ParentProxy<Child2>
      {
          int childMember;
      };
      
      int main()
      {
          Child1 child1;
          Child2 child2;
      
          Parent* objects[] = { &child1, &child2 };
          const int objectCount = sizeof(objects) / sizeof(objects[0]);
          for (int index = 0; index < objectCount; ++index)
          {
              Parent* parent = objects[index];
              parent->DoSomething();
          }
      }
      

      我更改了可编译的代码 - 可能不是您的要求 - 但随后提供了更好的(=可编译的)示例代码。

      【讨论】:

      • 我认为你甚至可以在 ParentProxy::DoSomething() 中使用“static_cast”。如果所有子类中的成员变量名称不相同,这也会有问题,并且对于某些类无法编译...也许您可以添加一些 SFINAE 魔术并有几个涵盖不同成员名称的变体。
      【解决方案4】:

      是否可以选择包含您需要访问的 childMember 的中间抽象类?

      如果是这样:

      class Parent
      {
          virtual void DoSomething() //Parent must be a polymorphing type to allow dyn_casting
          {
              if (AbstractChild* child = dynamic_cast<AbstractChild*>(this)) {
                  child->childMember = 0;
              } else //Its not an AbstractChild
          }
      }
      
      class AbstractChild : Parent 
      { 
      
           int childMember; 
           virtual void DoSomething() = 0;
      }
      
      class Child1 : AbstractChild {
           virtual void DoSomething() { }
      }
      

      【讨论】:

      • 这将允许您创建从父类派生的类,而无需强制它们也从 AbstractChild 派生
      【解决方案5】:

      您可以使用curiously recurring template pattern 来实现此目的。

      template<typename T>
      class Parent
      {
          void DoSomething()
          {
              // This is what I need
              T::childMember = 0;
          }
      
          virtual ~Parent() {}
      };
      
      class Child1 : Parent<Child1>
      {
        int childMember;
      
        friend class Parent<Child1>;
      };
      

      【讨论】:

      • 这看起来很有趣,尽管它仍然需要我更改我试图避免的子类定义。谢谢
      • @unexplored 是你不能修改派生类还是你不想,因为有这么多。如果是后者,则可以使用正则表达式搜索和替换进行上述更改,并且 IMO 是一个比必须维护大量 if-else if 构造(如已接受的答案中提出的构造)更好的选择。
      【解决方案6】:

      显然你有一些派生类的子集使用childMember。 对于这些类,您有一些可以调用的方法“DoSomething()”。我猜对于所有其他派生类,方法DoSomething() 不适用。为什么不为这组派生类创建另一个抽象级别?

      class Parent
      {
          // no DoSomething()
      }
      
      class ChildMemberClasses : Parent
      {
          int childMember;
      
          void DoSomething()
          {
              // code that uses childMember
          }
      }
      
      class ChildWithChildMember : ChildMemberClasses
      {
          // other stuff
      }
      

      如果DoSomething() 对没有childMember 的类有一些意义,你仍然可以在Parent 中将它定义为虚方法。像这样:

      class Parent
      {
          virtual void DoSomething()
          {
              // code that does not use childMember
          }
      }
      
      class ChildMemberClasses : Parent
      {
          int childMember;
      
          void DoSomething()
          {
              // code that uses childMember
          }
      }
      
      class ChildWithChildMember : ChildMemberClasses
      {
          // other stuff
      }
      

      【讨论】:

      • OP 试图避免修改子类,因为它们有 200 多个。但似乎OP不再有选择了。 :-(
      • @Chris 对,但在另一种情况下,OP 必须编写超过 200 个 if/else if 分支。在这种情况下,我更喜欢重构,因为问题不会在未来消失,并且每次有人派生一个类时更改父类的代码(这显然经常发生)既费时又容易出错。
      • 哦,我 100% 同意(请参阅我对 @john 的评论,关于 else if 分支更像是一个反例)。 OP也同意。
      • 是的,没看到下面的评论 :)
      【解决方案7】:

      唯一干净的方法是使用虚函数方法。

      如果Parent 类至少有一个虚函数(不一定是DoSomething),还有一种很糟糕的方法:

      void DoSomething() {
          if (Child1* child = dynamic_cast<Child1*>(this)) {
              child->childMember = 0;
          } else if (Child2* child = dynamic_cast<Child2*>(this)) {
              child->childMember = 0;
          } // and so on, and so forth
      }
      

      (如果Parent 没有虚函数,那么dynamic_cast 将不起作用。不过,任何设计为从中继承的类都应该至少有一个虚函数,即使它只是析构函数。)

      【讨论】:

      • 这种恶心的方式只适用于一个特定的子类。 OP 说有“数百个”子类。
      • @Oli:所以有数百个else if 分支。我确实说恶心。 :-)
      • 如果我错了,请纠正我,但我认为在这种情况下,我需要确切知道我正在处理的孩子的名字,对吧?如果是这样,那么我没有太多选择,而是实现一个虚拟函数,强制所有子类处理它们自己的属性(在> 200个类中这样做不是很有趣:()
      • 也许您实际上应该花时间考虑设计,并提出更改...从单个基类派生的 200 多个类看起来有点像太多的类。
      • 如果 childMember 受保护/私有怎么办?
      【解决方案8】:

      static_cast&lt;Child*&gt;(this)-&gt;childMember = 0; 应该可以工作。

      【讨论】:

      • 如果this 不是Child,那么你会得到UB。 :-D
      • 问题是有很多不同的子类,而不仅仅是一个。
      • @Oli:没错。所以这种方法双重行不通。 (我更新了我的答案来描述你如何使用dynamic_cast 来做到这一点,尽管非常糟糕。)
      • 那么我看不到任何虚拟功能的替代品。也许这是进行适当重构的好机会。我最喜欢的编程活动。
      • @john:我同意,我认为“数百个else if 分支”方法比实际做的任何事情都更像是一个反例。
      【解决方案9】:

      没有安全、可靠的方法可以使用指针来执行此操作。您可以使用巧妙的指针偏移来解决问题,但这将依赖于 childMember 出现在所有子类中的同一位置。

      【讨论】:

        猜你喜欢
        • 2013-05-23
        • 1970-01-01
        • 2014-07-13
        • 1970-01-01
        • 2018-07-22
        • 1970-01-01
        • 2010-11-09
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多