c++ - 将类类型映射到 C++ 中的其他类类型

标签 c++ class rtti

给定以下类型层次结构

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)。

另一种选择是给 OurDerivedTheirDerived 等一个唯一的标签,然后使用 switch case 构造创建 MySpecialYourSpecial

在 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/

相关文章:

c++ - dynamic_cast的实际使用?

c++ - PostThreadMessage 将 GetLastError 设置为 1444

c++ - 从第三方类实现虚函数

Delphi 没有足够的 RTTI 可用于支持此操作

c++ - 异构数组中的元素可以知道它是什么模板类型吗?

C++ RTTI 继承导致类大小增加

c++ - 将二叉树排序为排序数组

C++ 将 vector 传递给重载类 - 有些调用语句有效,有些则无效。

c# - DAL 类(class)应该公开吗?

c++ - 需要帮助足球模拟