如何从包含基类指针的容器中调用派生类函数(基于其类型)?
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;
现在我们不再关心我们用于实现 Z
或 Flag
的特定派生类型。
从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 != 0
、bool
的流式传输按字母顺序打印。你的do_the_right_thing
可以说应该是std::ostream&
。
这确实很有争议:) 是的,你是对的,但方法名称表明我认为这里缺少什么:OP 对派生类做什么的考虑!因此,我非常感谢您发现我的错误,但话又说回来,由 OP 根据其应用程序的目的定义一个合理的接口。以上是关于如何从包含基类指针的容器中调用派生类函数(基于其类型)?的主要内容,如果未能解决你的问题,请参考以下文章