【问题标题】:Nested struct attributes inheritance嵌套结构属性继承
【发布时间】:2020-01-19 17:05:45
【问题描述】:

我读到您可以使用继承来扩展结构。我有一个派生类,我希望在其中具有与其父级相同的结构,但扩展了更多字段。这似乎有效,但是当我从修改结构的 Parent 类调用方法时,它在 Child 结构属性中没有效果。这是我正在尝试的示例:

class Parent
{
  public:

    struct info 
    {
      int a;
    };

    info data;
    virtual void main(void);

};

void Parent::main()
{
  data.a =1;
}

class Child: public Parent
{
  public:

  struct info2: public info
  {
    int b;
  };

  info2 data;
  virtual void main(void);
};

void Child::main(void)
{
  Parent::main();
  data.b = 2;
  std::cout << data.a << "\n";
  std::cout << data.b << "\n";
}

int main(void)
{
  Parent *base;
  Child derived;
  base = &derived;

  base->main();

  return 0;
}

这不是打印1 and 2 而是打印0 and 2。所以基本上就好像派生类的属性数据没有被Parent::main的调用修改。

这样做的正确方法是什么?我完全做错了吗?

【问题讨论】:

  • 顺便说一句,参数列表中的void 在 C++ 中没有效果,这与在 C 中不同。

标签: c++ class inheritance polymorphism virtual


【解决方案1】:

你是说

void Child::main(void)
{
  Parent::main();
  data.b = 2;
  std::cout << Parent::data.a << "\n";
  std::cout << data.b << "\n";
}

派生类中声明的名称data 隐藏了基类中声明的名称data。所以你需要使用限定名来访问父类的隐藏成员。

至于派生类的成员数据的数据成员a则没有初始化。

派生类的对象有两个数据成员data:一个继承了类型info(其名称隐藏在派生类中),另一个是派生类自己的数据成员。

基类对派生类的数据成员data一无所知。

你可以在类信息中定义一个虚函数。例如

#include <iostream>
class Parent
{
  public:

    struct info 
    {
      int a;
      virtual void set( int a )
      {
        this->a = a;
      }
    };

    info data;
    virtual void main(void);

};

void Parent::main()
{
  data.set( 1 );
}

class Child: public Parent
{
  public:

  struct info2: public info
  {
    int b;
    void set( int a ) override
    {
        this->a = a;
    }
  };

  info2 data;
  virtual void main(void);
};

void Child::main(void)
{
    data.set( 3 );
  data.b = 2;
  std::cout << data.a << "\n";
  std::cout << data.b << "\n";
}

int main(void)
{
  Parent *base;
  Child derived;
  base = &derived;

  base->main();

  return 0;
}

程序输出是

3
2

【讨论】:

  • 我不能让Child 中的data.a 与父级相同吗?不知何故,我希望孩子拥有由父母填充的 data.a 并将其包含在 Childs 结构中。
  • @edgarstack 在父类中,您没有初始化数据成员 a。基类对派生类的新结构 info2 一无所知。
  • 嗯,我要Parent::main()方法修改Child::data.a。有没有办法做到这一点?
  • @edgarstack 不,没有办法。基类对其派生类拥有哪些新数据成员一无所知。
  • 所以也许我从错误的角度来处理这个问题。有没有更好/更正确的方法来做我想做的事情?所以基本上:父类有一个由其方法修改的结构。然后孩子想在相同的结构中添加一些对孩子唯一的新信息。但是,我想结合(并重用)对 Parent 方法的调用与 Child 方法来填充结构。
【解决方案2】:

您必须使用以下代码:

void Child::main(void)
{
    Parent::main();
    data.b = 2;
    std::cout << Parent::data.a << "\n";
    std::cout << data.b << "\n";
}

【讨论】:

    【解决方案3】:

    您完全正确,Parent::main() 无法访问Child::data,并且对任何神秘的info2 类型一无所知;对它来说,Parent::data 就是它的全部,info 是它的类型。

    有一些简单的方法可以让Child::main()Child::data 一起工作而不是Parent::data,或者让它从每个版本访问所需的字段,但我怀疑这不是你想要的。如果您希望ParentChild 看到相同的data(分别作为infoinfo2),那么data 本身应该多态使用。对于这个例子,为了简单起见,我将使用常规指针(反过来,operator. 将被替换为 operator-&gt;,当访问 data 的成员时),但我建议查看智能指针,例如 @ 987654340@来简化内存管理。

    class Parent
    {
      public:
    
        struct info 
        {
          int a;
    
          // Chances are, it's going to be deleted through an info* no matter what it is.  Therefore, virtual destructor.
          virtual ~info() = default;
        };
    
        info* data;  // Consider using a smart pointer here, like std::unique_ptr.
        virtual void main(void);
        virtual void output() const; // Just adding this for convenience.
    
        // Default constructor now allows data to be supplied, or creates it if necessary.
        Parent(info* dp = nullptr) : data(dp ? dp : new info) {}
        // Correct destructor will always be called.
        virtual ~Parent() { if(data) { delete data; } }
    
    };
    
    void Parent::main()
    {
      data->a =1;
    }
    

    我们现在删除字段Child::data,而是让Child 将其所需的data 提供给Parent 的构造函数。

    class Child: public Parent
    {
      public:
    
      struct info2: public info
      {
        int b;
      };
    
      //info2 data;
      virtual void main(void);
      void output() const override; // Just adding this for convenience.
    
      Child() : Parent(new info2) {}
    };
    

    Child 将在需要时将data 视为info2 而不是info

    void Child::main(void)
    {
      Parent::main();
      auto dataPtr = static_cast<info2*>(data); // In Child, we know data is an info2*.
      dataPtr->b = 2;
    
      // Just gonna move these to output(), for a cleaner illustration.
      //std::cout << "Data->a: " << data->a << "\n";
      //std::cout << "Data->b: " << dataPtr->b << "\n";
    }
    

    这将导致 data 按需要工作,ParentChild 都具有正确的类型。

    void Parent::output() const {
      std::cout << "Parent:\n";
      std::cout << "> Data->a: " << data->a << "\n";
    }
    
    void Child::output() const /*override*/ {
      std::cout << "Child as ";
      Parent::output();
    
      auto dataPtr = static_cast<info2*>(data);
      std::cout << "Child:\n";
      std::cout << "> Data->a: " << dataPtr->a << "\n";
      std::cout << "> Data->b: " << dataPtr->b << "\n";
    }
    

    这将按预期执行,如 live on Coliru 所示。请注意,例如,如果您希望能够从预先存在的 Parent 创建一个 Child,您需要添加一个移动构造函数,该构造函数可以从一个 info 创建一个 info2;您应该考虑遵循Rule of Five,或者使用智能指针而不是原始指针。 ;P

    【讨论】:

    • 太棒了!这就是我想要的。有什么地方我可以正确学习这些东西吗?我认为我不会在标准的 C++ 书中找到这一点。
    • 我敢肯定有一些书至少可以教授这些东西,@edgarstack;如果没有在任何地方讨论三/五/零规则,我会感到惊讶。除此之外,CPRPReference.com 是一个大部分准确的、稍微易于阅读的 C++ 标准参考,而 CPlusPlus.com 比它更易于阅读(尽管有些过时)。 CProgramming.com 等网站上有在线教程,这里有很多很好的解释(如果你能找到的话)。不过很多时候,实验是你最好的老师,很多像这样复杂的技巧只是知道如何使用......
    • ...您更有可能在其他地方找到解释的简单事物。 (例如,在这种情况下,我只是应用您正在学习的相同原则,继承和多态性。通过此处的指针访问data 的字段类似于base-&gt;main() 动态选择正确版本的@ 987654362@ 调用,即使 base 是 `Parent*`(因为每个 Child 也是一个 Parent,或者更机械地包含一个 Parent 用于锁定的指针);主要区别在于我们这样做动态调度自己工作,而不是由virtual 函数自动执行。
    • (另外请注意,在使用static_castinfo* 向下转换为info2* 时需要小心。通过static_cast 向下转换不会进行任何类型检查,并且因此仅适用于当您知道实际指向的类型 is-an info2(其中“is-a(n) X”表示“是 X 或派生自 X)时;因为 @987654376 @ 为data 显式提供了一个info2,我们可以保证这一点。如果您需要向下转换,您通常会使用dynamic_cast,如果对象的实际类型不是实际类型,这将为您提供nullptr你正在投射它。)
    猜你喜欢
    • 1970-01-01
    • 2021-12-18
    • 2019-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多