在C ++中将类类型映射到其他类类型

时间:2015-02-14 19:33:58

标签: 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在收到{{1}的实例时创建MySpecial },并在收到OurDerived

的实例时创建YourSpecial

我想我可以用TheirDerived做到这一点。它可能需要尝试重复投射接收的对象,直到找到匹配(非dynamic_cast返回)。

另一种选择是为nullptrOurDerived等提供唯一标记,然后使用TheirDerived switch构造来创建case,{{1}等等。

在C ++中是否有其他用于映射类类型的选项?

3 个答案:

答案 0 :(得分:3)

手动类型切换

如果您要创建的类型没有共同的祖先,则除了使用

之外没有其他选择
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

具有工厂功能的查找表

幸运的是,如果您要创建的所有类型都是从公共基类派生的,那么这不是您必须要做的,正如您在注释中所指出的那样。在这种情况下,您可以将对象的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来执行上述示例中工厂lambdas所做的事情。

如果这似乎具有侵扰性(可能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;
}

请注意我们如何很好地打破了BaseGeneral之间的耦合。在不利方面,我们必须通过BaseVisitor类引入某种父母对子的依赖。

这个解决方案也完全摆脱了任何明确的运行时类型推断,并且优雅地让动态调度机器在幕后完成所有魔术。

答案 1 :(得分:2)

是的,您可以将类型映射委托给派生类:

class Base
{
public:
   virtual General* map() = 0;
};
class OurDerived: public Base
{
protected:
   General* map()
   {
      // compute Type* for OurDerved
   }
};
class TheirDerived: public Base
{
protected:
   General* map()
   {
      // compute Type* for TheirDerived
   }
};

答案 2 :(得分:1)

如果不知道你的职能有什么责任,或者你对{My|Your}Special{Our|Their}Derived的联系感觉如何,很难说。

Base是可构建的吗? Base或其派生类是否允许使用虚方法?如果您已经承担了vtable的成本,我会将责任委托给派生类型本身,并在Base上显式地使方法抽象,以强制每个派生在这方面解释自己。

类型层次结构中的MySpcial / YourSpecial是否相关?否则,最好尝试使用辅助函数的显式模板实例化。