【问题标题】:When is a child class too different to be inherited?什么时候子类太不同而不能被继承?
【发布时间】:2021-06-24 16:06:24
【问题描述】:

我有一组紧密相连的数据结构,感觉它们应该被继承。但是,它们的不同之处在于孩子有一些额外的变量。我可以通过以下方式实现:

struct A {
int a;
int b;
virtual int kind()=0;
A(int a_, int b_) : a{a_}, b{b_} {};
}

struct B: A {
int c;
int kind() {return 1;};
B(int a_, int b_, int c_) : A(a_, b_), c{c_} {};
}

struct C : A {
int d;
int kind() {return 2;};
C(int a_, int b_, int d_) : A(a_, b_), d{d_} {};
}

但是如果我想遍历这些向量并访问派生类的变量,它开始变得有点混乱:

std::vector<A*> v;
v.push_back(new B(1,1,1));
v.push_back(new C(1,1,2));

for (A* bar : v) {
    if (bar->kind() == 1) {
        auto *bar_ = dynamic_cast<B*>(A);
        std::cout << bar_->c << std::endl;
    } else if (bar->kind() == 2) {
        auto *bar_ = dynamic_cast<C*>(A);
        std::cout << bar_->d << std::endl;
    }
}


这种事情是正常的,还是我错过了什么并违反了一些重要的规则?我发现的大多数继承示例没有不同的重要成员变量或方法,只是相同的实现方式不同。

【问题讨论】:

  • 作为一个好的经验法则,任何dynamic_cast 的使用都是一个危险信号:你的设计有问题。
  • 我会说这是一个糟糕的设计选择。如果您有指向基类的指针并希望通过它访问对象,您应该有一个统一的接口。所以可以处理所有对象,而不必关心它们是什么类型。否则,也许您应该以不同的方式开始存储它们。
  • 在不相关的注释中,您应该使用std::vector&lt;std::unique_ptr&lt;A&gt;&gt;,这样您就不必处理手动内存管理。
  • dynamic_cast&lt;B *&gt;(A); 很奇怪,你应该转换一个变量,而不是一个类型,你的意思可能是auto *bar_ = dynamic_cast&lt;B *&gt;(bar);
  • 根据经验,使用dynamic_cast 向下转换(例如将Base * 转换为Derived *)是一个非常重要的提示,即派生类不应从基类派生.在您的情况下,一个更简单的设计是基类提供一个名为(例如)toString() 的 virtual 函数,以将对象的所有数据打印到字符串和(如果需要)toDerivedString() 执行相同的操作,但仅适用于最派生的类成员(默认情况下不打印任何内容)。然后,您的循环可以直接调用这些函数 - 无需测试对象的实际类型。

标签: c++ pointers inheritance vector polymorphism


【解决方案1】:

您已经完成了创建虚拟基类的工作。除了将类型作为值返回之外,您还可以重载要执行单独操作的函数。

struct A {
  virtual ~A() = default;
  int a;
  int b;
  virtual int get_value() const = 0;
  A(int a_, int b_) : a{a_}, b{b_} {};
}

struct B: A {
  int c;
  int get_value() const override {return c;};
  B(int a_, int b_, int c_) : A(a_, b_), c{c_} {};
}

struct C : A {
  int d;
  int get_value() const override {return d;};
  C(int a_, int b_, int d_) : A(a_, b_), d{d_} {};
}

请注意,我在这里使用了override 说明符。这在现代 c++ 中是一种很好的做法,可提高可读性和代码安全性。

现在,我们可以使用虚函数来简化主代码

std::vector<A*> v;
v.push_back(new B(1,1,1));
v.push_back(new C(1,1,2));
        
for (auto* bar : v) {
  std::cout << bar->get_value() << std::endl;
}

// ... do something else with v

// clean up the memory

for (auto *bar : v) {
  delete bar;
}

记得在基类中定义一个虚析构函数。这样可以确保当我们删除向量v 中的A* 时,继承类的析构函数也会被调用,并且不会发生内存泄漏。

【讨论】:

  • 使用std::vector&lt;std::unique_ptr&lt;A&gt;&gt; v;代替手动删除循环,删除将自动发生。 (您也必须在主循环中设置为 auto&amp; bar : v)。
  • 是的,我总是会在现代代码中推荐一个智能指针,但是我不想改变基本容器结构远离原始问题,因为使用原始指针可能是 OP 的唯一选择.
猜你喜欢
  • 2016-05-29
  • 2017-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多