尝试用多态进行预测,出了什么问题?

时间:2016-11-09 16:37:43

标签: c++

我有两个指向基类的指针,一个指向实际的基础对象,另一个指向派生对象。我还有一个为基类和派生类重载的非成员函数。我想使用多态来向下转换指针,以便调用正确的重载。

代码的输出是

stretch

但是所需的输出是

base downcast called
base
derived downcast called
base

有人可以解释输出,以及必须采取哪些措施才能获得所需的行为?

base
derived

编辑:将cout添加到downcast函数以说明函数覆盖/多态。

3 个答案:

答案 0 :(得分:4)

您基本上是尝试使运行时多态性影响编译时重载解析。出于明显的原因,这是不可能的。函数重载是一个编译时功能,这意味着在编译时基于 static 类型的函数参数执行重载解析。

在您的情况下,选择要调用的foo是基于静态典型: static 类型ptr1和{{1 }和 static 类型的ptr2ptr1->downcast()返回值。后者是两种情况下类型ptr2->downcast()的左值(引用)。选择base时没有涉及多态性。编译器不知道(并且不关心)在运行时,这些foo引用之一实际上将引用base &对象。

但是,如果您可以从derived拨打a.downcast(),您会发现朗姆酒时间多态仍然可以在foo内部运行。 (此时由于foo为非常量而无法进行此类调用)

答案 1 :(得分:1)

你需要dynamic_cast。你正在努力做的事情不会起作用。

C ++允许对重写函数的协变返回值。由于derived继承自base,因此符合条件。但是如果你调用函数的基础版本,你仍然可以得到IT类型返回的类型,而不是派生类的版本。要获得该返回类型,您必须已经转换,以便编译器知道该函数返回的类型。

答案 2 :(得分:0)

所以我们可以用一种通用的方式做到这一点。

template<class...>struct types{};

template<std::size_t I>using index=std::integral_constant<std::size_t, I>;

template<class T, class types>
struct get_index_of_type;
template<class T, class...Ts>
struct get_index_of_type<T, types<T,Ts...>>:
  index<0>
{};
template<class T, class U, class...Ts>
struct get_index_of_type<T, types<U,Ts...>>:
  index<get_index_of_type<T, types<Ts...>>{}+1>
{};

template<class R, class Types>
struct dynamic_dispatch;
template<class R, class...Ts>
struct dynamic_dispatch<R, types<Ts...>>
{
  using fptr = R(*)(void const* pf, void* t);

  template<class F>
  std::array<fptr, sizeof...(Ts)>
  make_table() const {
    return {{
      +[](void const* pf, void* t)->R{
        auto* pt = static_cast< std::remove_reference_t<Ts>* >(t);
        auto* f = static_cast< std::remove_reference_t<F> const* >(pf);
        return (*f)(static_cast<Ts&&>(*pt));
      }...
    }};
  }
  void const* pf = nullptr; 
  std::array<fptr, sizeof...(Ts)> table;

  dynamic_dispatch( dynamic_dispatch&& )=default;
  dynamic_dispatch( dynamic_dispatch const& )=default;
  dynamic_dispatch& operator=( dynamic_dispatch&& )=default;
  dynamic_dispatch& operator=( dynamic_dispatch const& )=default;

  template<class F,
    std::enable_if_t< !std::is_same<std::decay_t<F>, dynamic_dispatch>{}, int> =0
  >
  dynamic_dispatch( F&& f ):
    pf(std::addressof(f)),
    table( make_table<std::decay_t<F>>() )
  {}

  template<class T>
  R operator()( T&& t ) const {
    return table[get_index_of_type<T,types<Ts...>>{}]( pf, std::addressof(t) );
  }
};

dynamic_dispatch<R, types<a,b,c>>接受任何可以使用abc中的任何一个调用的callable(确切类型,包括l / r值和const所需,所以make你的列表详细。没有完成隐式转换;这可以通过更多的工作来解决。)

现在,在名为base的{​​{1}}中添加方法:

apply

在派生中覆盖它:

virtual void apply( dynamic_dispatch<void, types<base*, derived*>> f ) {
  return f(this);
}

同一个身体。

现在主要:

virtual void apply( dynamic_dispatch<void, types<base*, derived*>> f ) override {
  return f(this);
}

live example

为了进一步阅读,我将删除的动态调度键入到函数对象的类型列表中。我为这个调度创建了一个视图。

auto super_foo = [](auto* x) { return foo(*x); }; int main() { base* ptr1 = new(base); base* ptr2 = new(derived); ptr1->apply(super_foo); ptr2->apply(super_foo); } 是一个单独的对象,表示super_foo的整个重载集,允许它作为一个参数传递。

这也可以通过访客模式更常规地完成:

foo

然后您实施struct visitor { void invoke( base* ) const = 0; void invoke( derived* ) const = 0; };

foo_visitor

我们写了一个struct foo_visitor:visitor { void invoke( base* a ) const {return foo(*a);} void invoke( derived* a ) const {return foo(*a);} }; ,其中包含applyvisitor&

请注意,这种技术,特别是如果您巧妙地在.invoke(this)之间进行选择而不是要求完全匹配,则允许通过递归实现多个调度多态(或者,您可以将Ts更改为{{1多个类型包)。