C++ 杂谈(13)

我们前面发现 apply 方法如果仅仅接受一个 std::function<void (Child*)> 会有问题,因为除了 composite 以外,另外两个 structure(我们分别叫 chain 和 contra_variance)都会因为类型不匹配而不能遍历我们的 extractor 结构。转念一想,所有的指针类型都能转换到 void,那是不是意味着我们可以将 void* 作为使用的 signature 呢?

答案是否定的。dynamic_cast 不能够将 void* 转换到别的类型,但是可以将别的类型转到 void*(参看这一篇这一篇):

  • 前者因为 void* 不是 polymorphic 的,因此 dynamic_cast 没法转
  • 后者转换获得的是这个对象首址,这在多继承下很有用,比如继承 A 和 B 之后如果用 B 的指针指向这个对象通过 dynamic_cast 变为 void*,值很可能不同

那么我们是否需要一个能将任意 polymorphic 类型的指针都能装承的类型呢?比如说我们有个 any_ptr 放在 Child* 的位置,这样在这个 function 实现里面通过某种 casting 测试是否成功就能实现我们需要的结果了。记得 boost.any 吗?似乎我们可以借助它的想法?

为了不把整个问题复杂化,我们先揭晓一个更为直接和 ws 的策略:我们为 extractor 提供一个公用的父类,这样 std::function<void (basic_extractor*)> 就可以为我们提供需要的结果了。

可是同样的问题是:是否可能提供一个类型,它可以存储任意的多态指针,并且将其 cast back 或者 dynamic_cast 到一个子类?我们参考了下面这篇文章

文中提到 boost.any 依赖了所谓的 external polymorphism,这个 pattern 的意思是说为了为所有的类型提供一个统一的 interface,而这些类型都没有 parent,于是将这个 interface 写做一个 interface class,然后构造模板继承并实现这个 interface

struct interface {
  virtual void do_something();
};

template <typename T>
class concrete : public interface {
public:
  void do_something() override;
private:
  T* ptr;
};

为了在 concrete 里实现 do_something,我们可以将其 delegate 到另外的一个简化的模板函数上,通过特化为不同的 T 实现这个过程。在 boost.any 中还使用了 typeid/type_info 来处理转换,问题是 type_info 类型并不支持 inheritance,这意味着如果 cast 到子类,通过 type_info 将不能匹配两者从而 fail。下面是一个简化的实现

class any {
public:
  template <typename T>
  T* cast() {
    if (typeid(T) == get_type_info()) {
      return static_cast<T>(ptr);
    } else {
      throw any_cast_error();
    }
  }

protected:
  virtual std::type_info get_type_info() const = 0;
  void* ptr;
};

template <typename T>
class any_impl : public any {
public:
  any_impl(T* p) : ptr(p) {}
protected:
  std::type_info get_type_info() const {
    return typeid(T);
  }
};

注意这里的精髓在于父类负责 output(cast) 的类型,子类负责原始类型(T*),两者的通讯是通过一个类型无关的 std::type_info 对象进行的。那么问题在于 std::type_info 不支持父类的查询,因此悲剧的来了,我们竟然不能将子类对象放进去,然后通过父类 cast 回来。因此我们需要另外一种策略。那篇文章指出,dynamic_cast 是 intrusive 的,要想捕获类型信息就得使用 type erased 的方式,而 C++ 的 RTTI 如此之弱让人一点办法都没有。有意思的事情是文章发现可以用 exception handling 来做和 dynamic_cast 类似的事情,这样一来子类和父类通讯的方式就变成没有类型的了,原文使用了简化到一层的方式(避免 virtual function call),我们这里沿用 external polymorphism,显得比较清晰:

class any {
public:
  template <typename T>
  T* cast() const {
    try {
      throw_it();
    } catch (T* p) {
      return p;
    } catch (...) {
      return nullptr;
    }
    return nullptr;
  }

  virtual ~any() {}

protected:
  virtual void throw_it() const = 0;
  void* ptr;
};

template <typename T>
class any_impl : public any {
public:
  any_impl(T* p) {
    ptr = p;
  }

protected:
  void throw_it() const override {
    throw static_cast<T*>(ptr);
  }
};

由于使用了 exception handling 可以想象效率要有多低就有多低 =.=

——————
And these are the sons of Reuel; Nahath, and Zerah, Shammah, and Mizzah:these were the sons of Bashemath Esau’s wife

Advertisements
C++ 杂谈(13)

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s