给定以下类型层次结构
class Base { public: virtual ~Base(); }
class OurDervied : public Base {}
class TheirDerived : public Base {}
class General { public: virtual ~General(); }
class MySpecial : public General {};
class YourSpecial : public General {};
我有一个函数 f(Base *bp)
。
在 f
中,我想创建一个类型取决于传入的类型的对象。例如,f
创建一个 MySpecial
当收到 OurDerived
的实例时,并在收到 TheirDerived
的实例时创建 YourSpecial
。
我想我可以用 dynamic_cast
做到这一点。它可能需要尝试反复转换接收到的对象,直到找到匹配项(返回非 nullptr
)。
另一种选择是给 OurDerived
、TheirDerived
等一个唯一的标签,然后使用 switch
case
构造创建 MySpecial
、YourSpecial
等
在 C++ 中是否有任何其他映射类类型的选项?
最佳答案
手动类型切换
如果您要创建的类型没有共同的祖先,您别无选择,只能使用
if (dynamic_cast<const DerivedA *>(&base))
{
// Create an object of some type.
}
else if (dynamic_cast<const DerivedB *>(&base))
{
// Create an object of some other type.
}
else if (dynamic_cast<const DerivedC *>(&base))
{
// Create an object of yet aother type.
}
else
{
// Handle the case that no type was matched. Maybe use a default or
// issue an error.
}
级联并且没有直接的方法可以返回创建的对象,因为函数无法在运行时决定它想要的返回类型。唯一的出路是使用 type erasure还是丑union
带有工厂函数的查找表
幸运的是,如果您要创建的所有类型都派生自公共(public)基类,这不是您必须做的,正如您在评论中指出的那样。在这种情况下,您可以映射 typeid
对象到创建适当对象的工厂函数。与运行时多态性一样,这需要堆分配。
void
take_action(const Base& base)
{
using FactoryT = std::function<std::unique_ptr<General>()>;
static const std::map<std::type_index, FactoryT> factories {
{typeid(DerivedA), [](){ return std::make_unique<Special1>(); }},
{typeid(DerivedB), [](){ return std::make_unique<Special2>(); }},
{typeid(DerivedC), [](){ return std::make_unique<Special3>(); }},
};
const auto o_uptr = factories.at(typeid(base))();
// Use the object. It can also be returned.
}
我做了 std::map<std::type_index, std::function<FactoryT()>>
static
所以它在程序的整个运行时只创建一次。目前尚不清楚这对您的特定情况是否有益。也许对其进行基准测试。
这是一个完整的工作示例。
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <typeindex>
#include <typeinfo>
struct Base
{
virtual ~Base() = default;
virtual std::string
name() const
{
return "Base";
}
};
std::ostream&
operator<<(std::ostream& os, const Base& base)
{
return os << base.name();
}
template<char Token>
struct Derived : Base
{
virtual std::string
name() const override
{
std::string name {"Derived"};
name += Token;
return name;
}
};
using DerivedA = Derived<'A'>;
using DerivedB = Derived<'B'>;
using DerivedC = Derived<'C'>;
struct General
{
virtual ~General() = default;
virtual std::string
name() const
{
return "General";
}
};
template<char Token>
struct Special : General
{
virtual std::string
name() const override
{
std::string name {"Special"};
name += Token;
return name;
}
};
std::ostream&
operator<<(std::ostream& os, const General& general)
{
return os << general.name();
}
using Special1 = Special<'1'>;
using Special2 = Special<'2'>;
using Special3 = Special<'3'>;
void
take_action(const Base& base)
{
using FactoryT = std::function<std::unique_ptr<General>()>;
static const std::map<std::type_index, FactoryT> factories {
{typeid(DerivedA), [](){ return std::make_unique<Special1>(); }},
{typeid(DerivedB), [](){ return std::make_unique<Special2>(); }},
{typeid(DerivedC), [](){ return std::make_unique<Special3>(); }},
};
const auto o_uptr = factories.at(typeid(base))();
std::cout << base << " was mapped to " << *o_uptr << std::endl;
}
int
main()
{
take_action(DerivedA {});
take_action(DerivedB {});
take_action(DerivedC {});
return 0;
}
输出:
DerivedA was mapped to Special1
DerivedB was mapped to Special2
DerivedC was mapped to Special3
访客模式
当然,你应该问问自己为什么你真的想这样做。肯定有这种技术的合法应用,但采用抽象类型然后根据其动态类型采取行动通常是过度抽象的标志,并且会导致代码难以维护。有没有考虑把工厂直接加到Base
?
struct Base
{
virtual ~Base() = default;
virtual std::unique_ptr<General>
getDealer() = 0;
// ...
};
Derived
然后类可以覆盖 getDealer
执行上述示例中工厂 lambda 执行的操作。
如果这看起来很麻烦(也许 Base
类根本不应该知道 General
类),您可以考虑使用 visitor pattern .这是一个多一点的工作,但允许更好的解耦。有关此模式的信息很多,因此我只会展示它在您的特定问题中的应用,如果您需要更多解释,请引用您最喜欢的搜索引擎。
#include <iostream>
#include <memory>
#include <string>
struct BaseVisitor;
struct Base
{
virtual ~Base() = default;
virtual void
accept(BaseVisitor&) const = 0;
virtual std::string
name() const
{
return "Base";
}
};
std::ostream&
operator<<(std::ostream& os, const Base& base)
{
return os << base.name();
}
template<char Token>
struct Derived : Base
{
virtual void
accept(BaseVisitor& vtor) const override;
virtual std::string
name() const override
{
std::string name {"Derived"};
name += Token;
return name;
}
};
using DerivedA = Derived<'A'>;
using DerivedB = Derived<'B'>;
using DerivedC = Derived<'C'>;
struct BaseVisitor
{
virtual ~BaseVisitor() = default;
virtual void
visit(const DerivedA&) = 0;
virtual void
visit(const DerivedB&) = 0;
virtual void
visit(const DerivedC&) = 0;
};
// Cannot be defined earlier because we need the complete type of BaseVisitor.
template<char Token>
void
Derived<Token>::accept(BaseVisitor& vtor) const
{
vtor.visit(*this);
}
struct General
{
virtual ~General() = default;
virtual std::string
name() const
{
return "General";
}
};
template<char Token>
struct Special : General
{
virtual std::string
name() const override
{
std::string name {"Special"};
name += Token;
return name;
}
};
std::ostream&
operator<<(std::ostream& os, const General& general)
{
return os << general.name();
}
using Special1 = Special<'1'>;
using Special2 = Special<'2'>;
using Special3 = Special<'3'>;
void
take_action(const Base& base)
{
struct Mapper : BaseVisitor
{
std::unique_ptr<General> uptr {};
virtual void
visit(const DerivedA&) override
{
this->uptr.reset(new Special1 {});
}
virtual void
visit(const DerivedB&) override
{
this->uptr.reset(new Special2 {});
}
virtual void
visit(const DerivedC&) override
{
this->uptr.reset(new Special3 {});
}
};
Mapper visitor {};
base.accept(visitor);
std::cout << base << " was mapped to " << *visitor.uptr << std::endl;
}
int
main()
{
take_action(DerivedA {});
take_action(DerivedB {});
take_action(DerivedC {});
return 0;
}
请注意我们如何很好地打破 Base
之间的耦合和 General
.不利的一面是,我们不得不通过 BaseVisitor
引入某种父子依赖关系。类。
此解决方案还完全摆脱了任何显式运行时类型推断,并优雅地让动态调度机制在幕后完成所有魔法。
关于c++ - 将类类型映射到 C++ 中的其他类类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28519430/