7

我正在阅读Andrei Alexandrescu所应用的Modern C++ Design Generic Programming and Design Patterns关于多方法的第 11 章,这正是我试图解决的问题。本书的所有源代码都发布在一个名为Loki的库中。

问题是这本书相当旧(2001 年),并且处理了C++11 中不再适用的限制(例如,模板参数的数量不能是可变的)。我试图查找 Loki 是否已使用 C++11 重写,但最后一次修改日期为 2009 年,并且Andrei Alexandrescu 的主页上没有更新。然而,在做了一些研究之后,我觉得 Loki 有点像 Boost,因为它被合并到了标准库中。

多方法习语或它的某些部分是否已被 C++11 采用?

4

2 回答 2

5

它不在标准中,但使用由一对类型标识索引的函数对象映射相当容易构建。

为了完整起见,这是我的第一次尝试:

#include <iostream>
#include <typeinfo>
#include <typeindex>
#include <map>
#include <functional>
#include <memory>

struct Animal {
    virtual std::type_index type() const = 0;
};

template <class T> struct AnimalImpl : public Animal {
    std::type_index type() const override {
        return typeid(T);
    }
};

struct Dog : AnimalImpl<Dog> {

};

struct Cat : AnimalImpl<Cat> {

};

struct Mouse : AnimalImpl<Mouse> {

};

using Types = std::tuple<std::type_index, std::type_index>;
using Outcome = std::function<void (Animal&, Animal&)>;
using DispatchMap = std::map<Types, Outcome>;

using namespace std;


void catVDog(Animal& cat, Animal& dog) {
    cout << "dog wins\n";
}

void catVMouse(Animal& cat, Animal& mouse)
{
    cout << "cat wins\n";
}

DispatchMap makeOutcomes()
{
    DispatchMap result;
    result.emplace( make_pair( Types {typeid(Cat), typeid(Dog)}, catVDog) );
    result.emplace( make_pair( Types {typeid(Dog), typeid(Cat)}, 
        [](Animal&a1,Animal&a2) { return catVDog(a2,a1); }) );

    result.emplace( make_pair( Types {typeid(Cat), typeid(Mouse)}, catVMouse) );
    result.emplace( make_pair( Types {typeid(Mouse), typeid(Cat)}, 
        [](Animal&a1,Animal&a2) { return catVMouse(a2,a1); }) );
    return result;
}

const DispatchMap outcomes = makeOutcomes();

void fight(Animal& a1, Animal& a2)
{
    auto it = outcomes.find(Types{ a1.type(), a2.type() });
    if (it == outcomes.end()) {
        cout << typeid(a1).name() << " " << typeid(a2).name() << " ";
        std::cout << "no fight\n";
    }
    else {
        it->second(a1, a2);
    }
}


int main()
{
    unique_ptr<Animal> cat { new Cat {} };
    unique_ptr<Animal> dog { new Dog {} };
    unique_ptr<Animal> mouse { new Mouse {} };

    fight(*cat, *dog);
    fight(*cat, *mouse);
    fight(*dog, *cat);
    fight(*dog, *mouse);

   return 0;
}
于 2014-04-28T09:08:42.767 回答
1

基于本书的想法,我在 C++11 中实现了自己的多方法,我希望这个解决方案对某人有用。

MultiMethod 模板类使用多方法返回值类型和其多态参数的基类型进行参数化。该类表示具有 operator() 接口函数的抽象多方法。具体方法由添加模板函数注册。此函数模板参数是已注册方法参数的派生类型。

以下是MultiMethod类源码:

//////////////////////////////////////////////////////////////////////////////
// MultiMethod.h

#ifndef _MULTI_METHOD_H_
#define _MULTI_METHOD_H_

#include "TypeInfo.h"
#include <functional>
#include <tuple>
#include <map>

template <typename>
class MultiMethod;

template <typename Res, typename... ArgsBase>
class MultiMethod<Res(ArgsBase...)> {
  template <class T>
  using ArgId = TypeInfo;
  using CallbackId = std::tuple<ArgId<ArgsBase>...>;
  using Callback = std::function<Res(ArgsBase&...)>;
  using Callbacks = std::map<CallbackId, Callback>;
  Callbacks callbacks;
public:
  // Method registration.
  template <typename... Args, typename Fn>
  void Add(Fn fn) {
    callbacks[CallbackId(TypeInfo(typeid(Args))...)] = [fn](ArgsBase&... args) -> Res {
      return fn(dynamic_cast<Args&>(args)...);
    };
  }
  // Multimethod call.
  template <typename... Args>
  Res operator()(Args&... args) {
    auto it = callbacks.find(CallbackId(TypeInfo(typeid(args))...));
    if (it != callbacks.end()) {
      return it->second(args...);
    }
    return Callback()(args...);
  }
};

#endif // _MULTI_METHOD_H_

简约的辅助类 TypeInfo 用于识别具体的方法参数。该类在以下源代码中实现:

//////////////////////////////////////////////////////////////////////////////
// TypeInfo.h

#ifndef _TYPE_INFO_H_
#define _TYPE_INFO_H_

#include <typeinfo>

class TypeInfo {
  const std::type_info& ti;
public:
  TypeInfo(const std::type_info& ti) : ti(ti)
    {}
  friend bool operator<(const TypeInfo& t1, const TypeInfo& t2);
};

bool operator<(const TypeInfo& t1, const TypeInfo& t2);

#endif // _TYPE_INFO_H_

//////////////////////////////////////////////////////////////////////////////
// TypeInfo.cpp

#include "TypeInfo.h"

bool operator<(const TypeInfo& t1, const TypeInfo& t2)
  { return t1.ti.before(t2.ti); }

下面是使用 MultiMethod 类的示例:

//////////////////////////////////////////////////////////////////////////////
// main.cpp

#include "MultiMethod.h"
#include <iostream>
#include <memory>

// Number base class.
class Number {
public:
  virtual ~Number()
    {}
};

// Integer number class.
class Integer : public Number {
  int val;
public:
  Integer(int v) : val {v}
    {}
  int Value() const
    { return val; }
};

// Real number class.
class Real : public Number {
  double val;
public:
  Real(double v) : val {v}
    {}
  double Value() const
    { return val; }
};

int main(int argc, char* argv[]) {
  // Single number printing multimethod.
  MultiMethod<bool(Number)> print1;
  print1.Add<Real>(
    [](Real& r)
      { return (std::cout << r.Value() << std::endl, true); });
  print1.Add<Integer>(
    [](Integer& i)
      { return (std::cout << i.Value() << std::endl, true); });
  // Two numbers printing multimethod.
  MultiMethod<bool(Number, Number)> print2;
  print2.Add<Real, Real>(
    [&print2](Real& r1, Real& r2)
      { return (std::cout << r1.Value() << " " << r2.Value() << std::endl, true); });
  print2.Add<Real, Integer>(
    [&print2](Real& r1, Integer& i2)
      { return (std::cout << r1.Value() << " " << i2.Value() << std::endl, true); });
  print2.Add<Integer, Real>(
    [&print2](Integer& i1, Real& r2)
      { return (std::cout << i1.Value() << " " << r2.Value() << std::endl, true); });
  print2.Add<Integer, Integer>(
    [&print2](Integer& i1, Integer& i2)
      { return (std::cout << i1.Value() << " " << i2.Value() << std::endl, true); });
  // Two numbers addition multimethod.
  MultiMethod<std::unique_ptr<Number>(Number, Number)> add;
  add.Add<Real, Real>(
    [](Real& r1, Real& r2)
      { return std::unique_ptr<Number> {new Real {r1.Value() + r2.Value()}}; });
  add.Add<Integer, Integer>(
    [](Integer& i1, Integer& i2)
      { return std::unique_ptr<Number> {new Integer {i1.Value() + i2.Value()}}; });
  add.Add<Real, Integer>(
    [&add](Real& r1, Integer& i2)
      { return add(i2, r1); });
  add.Add<Integer, Real>(
    [&add](Integer& i1, Real& r2) {
      std::unique_ptr<Real> r1 {new Real(i1.Value())};
      return add(*r1, r2);
    }
  );
  // Multimethod call examples.
  std::unique_ptr<Number> n1 {new Real {12.3}};
  std::unique_ptr<Number> n2 {new Integer {4}};
  print1(*n1);
  print1(*n2);
  print2(*n1, *n1);
  print2(*n1, *n2);
  print2(*n2, *n1);
  print2(*n2, *n2);
  print1(*add(*n1, *n1));
  print1(*add(*n1, *n2));
  print1(*add(*n2, *n1));
  print1(*add(*n2, *n2));
  return 0;
}
于 2017-10-03T18:04:24.230 回答