当 child 存储在基指针向量上时如何从基动态转换为 child class
How to dynamic cast from base to child class when the child is stored on a vector of base pointers
我已将 Child object 的共享指针存储在 基本共享指针的向量 中,并且我需要将 Base 向量的元素动态转换为它的 Child 类型 ,这样我就可以 调用带有 [=41 的函数=] 具体签名。
下面是一个例子。第一个代码块定义了 class 层次结构和我想使用的 "identify" 函数。第二个代码块给出了一个具体示例,说明 我想如何调用 TYPE-specific "identify" 函数 ,假设我可以转换原始 object 类型从基础 class 到 Child class(例如 A、B、C)。
有什么模式或技术可以解决这个问题吗?
#include <iostream>
#include <memory>
#include <vector>
class Base {};
class A : public Base{};
class B : public Base{};
class C : public Base{};
class CollectionOfBase
{
public:
void add (std::shared_ptr<Base> item){m_items.push_back(item);}
std::vector<std::shared_ptr<Base>> const& getItems() const {return m_items;}
private:
std::vector<std::shared_ptr<Base>> m_items;
};
// I want to use these 3 functions instead of identify( std::shared_ptr<Base> const& )
void identify( std::shared_ptr<A> const& )
{
std::cout << "A" << std::endl;
}
void identify( std::shared_ptr<B> const& )
{
std::cout << "B" << std::endl;
}
void identify( std::shared_ptr<C> const& )
{
std::cout << "C" << std::endl;
}
//This function works in the below for loop, but this is not what I need to use
void identify( std::shared_ptr<Base> const& )
{
std::cout << "Base" << std::endl;
}
下面,您可以找到第二个代码块:
int main()
{
using namespace std;
CollectionOfBase collection;
collection.add(make_shared<A>());
collection.add(make_shared<A>());
collection.add(make_shared<C>());
collection.add(make_shared<B>());
for (auto const& x : collection.getItems())
{
// THE QUESTION:
// How to distinguish different type of items
// to invoke "identify" with object specific signatures (e.g. A,B,C) ???
// Can I cast somehow the object types that I push_back on my Collection ???
// Note that this loop does not know the add order AACB that we pushed the Child pointers.
identify(x);
}
/*
The desired output of this loop should be:
A
A
C
B
*/
return 0;
}
代码也可在 Ideone 上获得。
Visitor pattern 解决问题。
基本上,添加一个虚拟方法Base::accept(Visitor& v)
。
每个 child 都会覆盖这样的方法调用 v.visit(*this)
。
您的访客 class 应如下所示:
class Visitor
{
public:
void visit(A&) { /* this is A */ }
void visit(B&) { /* this is B */ }
void visit(C&) { /* this is C */ }
}
实例化您的访问者:Visitor v;
迭代你的向量调用 x->accept(v);
。
您可以在此处采用三种方法:OO 和动态调度、访问者和变体。哪一个更好将取决于你有多少 types 和你有多少 operations - 以及你更喜欢哪一个可能添加到。
实际使用OO。如果您需要每个派生对象以不同的方式执行某些操作,在 OO 中实现的方式是添加一个虚拟成员函数:
struct Base { virtual const char* name() = 0; };
struct A : Base { const char* name() override { return "A"; }
// ...
for (auto const& x : collection.getItems()) {
std::cout << x->name() << std::endl;
}
使用访客模式。这是 OO 和函数式之间的一半——我们创建了一个知道如何与所有类型交互的基础对象:
struct Visitor;
struct Base { virtual void visit(Visitor& ) = 0; };
struct A;
struct B;
struct C;
struct Visitor {
virtual void visit(A& ) = 0;
virtual void visit(B& ) = 0;
virtual void visit(C& ) = 0;
};
struct A : Base { void visit(Visitor& v) override { v.visit(*this); } };
// ...
struct IdentityVisitor : Visitor {
void visit(A& ) { std::cout << "A" << std::endl; }
void visit(B& ) { std::cout << "B" << std::endl; }
void visit(C& ) { std::cout << "C" << std::endl; }
};
IdentityVisitor iv;
for (auto const& x : collection.getItems()) {
x->visit(iv);
}
只需使用一个变体。不是存储 shared_ptr<Base>
的集合,而是存储 variant<A,B,C>
的集合,其中这些类型甚至不在层次结构中。它们只是三种任意类型。然后:
for (auto const& x : collection.getItems()) {
visit(overload(
[](A const& ){ std::cout << "A" << std::endl; },
[](B const& ){ std::cout << "B" << std::endl; },
[](C const& ){ std::cout << "C" << std::endl; }
), x);
}
@Barry 和@csguth 感谢你们的回答。
Barry的第三个选项很有意思,我想试试看。 boost::static_visitor 和 boost::variant 的工作示例可以在 [=10 上找到=].
就我而言,我没有考虑 OO 和虚拟方法,因为我想避免将逻辑放入这些 A、B、C 对象中。关于访问者模式,这是我想到的唯一好的选择。但是,我希望能找到一些更灵活的解决方案,例如 "LambdaVisitor",感谢您让我大开眼界!
我已将 Child object 的共享指针存储在 基本共享指针的向量 中,并且我需要将 Base 向量的元素动态转换为它的 Child 类型 ,这样我就可以 调用带有 [=41 的函数=] 具体签名。
下面是一个例子。第一个代码块定义了 class 层次结构和我想使用的 "identify" 函数。第二个代码块给出了一个具体示例,说明 我想如何调用 TYPE-specific "identify" 函数 ,假设我可以转换原始 object 类型从基础 class 到 Child class(例如 A、B、C)。
有什么模式或技术可以解决这个问题吗?
#include <iostream>
#include <memory>
#include <vector>
class Base {};
class A : public Base{};
class B : public Base{};
class C : public Base{};
class CollectionOfBase
{
public:
void add (std::shared_ptr<Base> item){m_items.push_back(item);}
std::vector<std::shared_ptr<Base>> const& getItems() const {return m_items;}
private:
std::vector<std::shared_ptr<Base>> m_items;
};
// I want to use these 3 functions instead of identify( std::shared_ptr<Base> const& )
void identify( std::shared_ptr<A> const& )
{
std::cout << "A" << std::endl;
}
void identify( std::shared_ptr<B> const& )
{
std::cout << "B" << std::endl;
}
void identify( std::shared_ptr<C> const& )
{
std::cout << "C" << std::endl;
}
//This function works in the below for loop, but this is not what I need to use
void identify( std::shared_ptr<Base> const& )
{
std::cout << "Base" << std::endl;
}
下面,您可以找到第二个代码块:
int main()
{
using namespace std;
CollectionOfBase collection;
collection.add(make_shared<A>());
collection.add(make_shared<A>());
collection.add(make_shared<C>());
collection.add(make_shared<B>());
for (auto const& x : collection.getItems())
{
// THE QUESTION:
// How to distinguish different type of items
// to invoke "identify" with object specific signatures (e.g. A,B,C) ???
// Can I cast somehow the object types that I push_back on my Collection ???
// Note that this loop does not know the add order AACB that we pushed the Child pointers.
identify(x);
}
/*
The desired output of this loop should be:
A
A
C
B
*/
return 0;
}
代码也可在 Ideone 上获得。
Visitor pattern 解决问题。
基本上,添加一个虚拟方法Base::accept(Visitor& v)
。
每个 child 都会覆盖这样的方法调用 v.visit(*this)
。
您的访客 class 应如下所示:
class Visitor
{
public:
void visit(A&) { /* this is A */ }
void visit(B&) { /* this is B */ }
void visit(C&) { /* this is C */ }
}
实例化您的访问者:Visitor v;
迭代你的向量调用 x->accept(v);
。
您可以在此处采用三种方法:OO 和动态调度、访问者和变体。哪一个更好将取决于你有多少 types 和你有多少 operations - 以及你更喜欢哪一个可能添加到。
实际使用OO。如果您需要每个派生对象以不同的方式执行某些操作,在 OO 中实现的方式是添加一个虚拟成员函数:
struct Base { virtual const char* name() = 0; }; struct A : Base { const char* name() override { return "A"; } // ... for (auto const& x : collection.getItems()) { std::cout << x->name() << std::endl; }
使用访客模式。这是 OO 和函数式之间的一半——我们创建了一个知道如何与所有类型交互的基础对象:
struct Visitor; struct Base { virtual void visit(Visitor& ) = 0; }; struct A; struct B; struct C; struct Visitor { virtual void visit(A& ) = 0; virtual void visit(B& ) = 0; virtual void visit(C& ) = 0; }; struct A : Base { void visit(Visitor& v) override { v.visit(*this); } }; // ... struct IdentityVisitor : Visitor { void visit(A& ) { std::cout << "A" << std::endl; } void visit(B& ) { std::cout << "B" << std::endl; } void visit(C& ) { std::cout << "C" << std::endl; } }; IdentityVisitor iv; for (auto const& x : collection.getItems()) { x->visit(iv); }
只需使用一个变体。不是存储
shared_ptr<Base>
的集合,而是存储variant<A,B,C>
的集合,其中这些类型甚至不在层次结构中。它们只是三种任意类型。然后:for (auto const& x : collection.getItems()) { visit(overload( [](A const& ){ std::cout << "A" << std::endl; }, [](B const& ){ std::cout << "B" << std::endl; }, [](C const& ){ std::cout << "C" << std::endl; } ), x); }
@Barry 和@csguth 感谢你们的回答。
Barry的第三个选项很有意思,我想试试看。 boost::static_visitor 和 boost::variant 的工作示例可以在 [=10 上找到=].
就我而言,我没有考虑 OO 和虚拟方法,因为我想避免将逻辑放入这些 A、B、C 对象中。关于访问者模式,这是我想到的唯一好的选择。但是,我希望能找到一些更灵活的解决方案,例如 "LambdaVisitor",感谢您让我大开眼界!