如何从包含基类指针的容器中调用派生类函数(基于其类型)?

Posted

技术标签:

【中文标题】如何从包含基类指针的容器中调用派生类函数(基于其类型)?【英文标题】:How to call derived class function (based on its type) from a container which contain pointer of base class? 【发布时间】:2017-08-01 19:21:25 【问题描述】:

我从 Base 类继承来创建两个不同的 Derived 类(Derived1 和 Derived2),然后将它们放入一个向量中。假设我想根据类的类型调用派生类的函数。

伪代码:

if holder[1] stored Derived1 then I want to call GetZ() 
else if holder[1] stored Derived2 then I want to GetFlag(). 

一次尝试:

#include <iostream>
#include <memory>
#include <vector>

class Base 
 public:
  Base(int x, int y) : x_(x), y_(y) 

  int GetX()  return x_; 
  int GetY()  return y_; 

 private:
  int x_;
  int y_;
;

class Derived1 : public Base 
 public:
  Derived1(int x, int y, int z) : Base(x, y), z_(z) 

  int GetZ()  return z_; 

 private:
  int z_;
;

class Derived2 : public Base 
 public:
  Derived2(int x, int y, bool flag) : Base(x, y), flag_(flag) 

  bool GetFlag()  return flag_; 

 private:
  bool flag_;
;

std::vector<std::shared_ptr<Base>> holder;
void print();
int main() 
  holder.push_back(std::make_shared<Derived1>(3, 4, 5));
  holder.push_back(std::make_shared<Derived2>(6, 7, true));

  print();


void print()

  for(auto& it : holder)
    // call this if "it" is Derived2
    // else call it->GetX()
    // currently this gives compilation error 
    // because of object slicing
    std::cout << it->GetFlag() << std::endl;
  


【问题讨论】:

如果您不能以提供相似接口的方式编写类,那么多态可能不是最好的方法。 另一种可能性是访问者模式。 【参考方案1】:
for(auto& it : holder)
  if (auto* D1 = dynamic_cast<Derived1*>(it->get())) 
    std::cout << D1->GetZ();
   else if (auto* D2 = dynamic_cast<Derived2*>(it->get())) 
    std::cout << D2->GetFlag();
  
  std::cout << std::endl;

动态转换通常是代码异味,表明您的界面Base 缺少功能。动态转换后,您的界面将从 Base 状态变为整个类型层次结构的布局和内容。

改为添加:

virtual boost::optional<int> GetZ()  return ; 
virtual boost::optional<bool> GetFlag()  return ; 

Base,并在派生中覆盖。

for(auto& it : holder)
  if (auto Z = it->GetZ()) 
    std::cout << *Z;
   else if (auto flag = it->GetFlag())
    std::cout << *flag;
  
  std::cout << std::endl;

现在我们不再关心我们用于实现 ZFlag 的特定派生类型。

从this SO answer有一个link to a reference std::optional implementation使用boost软件许可,是一个头文件。

【讨论】:

@vantamula 是的,你可以自己编写一个optional 的实现。或者获取 C++17 std::optional。或者从某个地方找到一个。 我知道这个问题有点离题..但是你能指出我正确的方向来实现我自己的optional @vantamula 我所做的是我选择了一篇关于 C++17 的可选论文,并复制了我需要的接口部分。然后我就写了。简单实现中的困难部分是在 std::aligned_storage 中放置 new。 @vantamula 链接已添加到具有合理许可(增强许可)的 std::optional 的参考实现中。您可能想稍微修改一下,以便它不在std 中。它比我写的一个简单的可选要复杂大约 10 到 20 倍,但它有更多的功能。【参考方案2】:

检查一下dynamic_cast 的妙招——如果它是可转换的,那么它就是正确的类型。

无论如何,我建议不要使用这种设计模式(在运行时检查类型并据此决定),而是将逻辑放入派生类中;有一个通用的方法可以做一件事或另一件事:

class Base 
 public:
  Base(int x, int y) : x_(x), y_(y) 

  int GetX()  return x_; 
  int GetY()  return y_; 

  virtual int do_the_right_thing();

 private:
  int x_;
  int y_;
;

class Derived1 : public Base 
 public:
  Derived1(int x, int y, int z) : Base(x, y), z_(z) 

  int GetZ()  return z_; 

  virtual int do_the_right_thing()  return GetZ() ;

 private:
  int z_;
;

class Derived2 : public Base 
 public:
  Derived2(int x, int y, bool flag) : Base(x, y), flag_(flag) 

  bool GetFlag()  return flag_; 

  virtual int do_the_right_thing()  return GetFlag() ;

 private:
  bool flag_;
;


void print()

  for(auto& it : holder)
    // call this if "it" is Derived2
    // else call it->GetX()
    // currently this gives compilation error 
    // because of object slicing
    std::cout << it->do_the_right_thing() << std::endl;
  


处理这个问题的 STL 风格的方式是模板和类型特征——但我个人认为这很麻烦。

【讨论】:

如果return GetFlag()bool 类型,是否会写入返回int? GetFlag() 返回一个bool,它不是int。例如,如果boolalpha != 0bool 的流式传输按字母顺序打印。你的do_the_right_thing 可以说应该是std::ostream&amp; 这确实很有争议:) 是的,你是对的,但方法名称表明我认为这里缺少什么:OP 对派生类做什么的考虑!因此,我非常感谢您发现我的错误,但话又说回来,由 OP 根据其应用程序的目的定义一个合理的接口。

以上是关于如何从包含基类指针的容器中调用派生类函数(基于其类型)?的主要内容,如果未能解决你的问题,请参考以下文章

如何从指向基类类型的指针调用派生类函数(C++)

如何从基类调用派生赋值运算符?

从基类函数派生的类容器

python 怎样调用基类函数

为啥指向基类的派生类指针可以调用派生类成员函数? [复制]

C++基类指针容器,编辑特定对象的属性