试图用多态性来降视,出了什么问题?

Trying to downcast with polymorphism, what's going wrong?

本文关键字:什么 问题 多态性      更新时间:2023-10-16

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

代码的输出是

base downcast called
base
derived downcast called
base

但是期望的输出是

base
derived

谁能解释一下输出,以及必须做些什么才能得到期望的行为?

#include <iostream>
using namespace std;
class base
{
public:
  virtual base& downcast()
  {
    cout << "base downcast called" << endl;
    return *this;
  }
};
class derived: public base
{
public:
  virtual derived& downcast()
  {
    cout << "derived downcast called" << endl;
    return *this;
  }
};
void foo(const base& a)
{
  cout << "base" << endl;
}
void foo(const derived& a)
{
  cout << "derived" << endl;
}

int main()
{
  base* ptr1 = new(base);
  base* ptr2 = new(derived);
  foo(ptr1->downcast());
  foo(ptr2->downcast());
  return 0;
}

编辑:添加cout到向下转换的函数,以说明函数覆盖/多态性

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

在您的情况下,选择调用哪个foo是基于static类型:ptr1ptr2static类型和ptr1->downcast()ptr2->downcast()static类型返回值。后者在两种情况下都是base类型的左值(引用)。选择foo不涉及多态性。编译器不知道(也不关心)在运行时这些base &引用中的一个实际上会引用derived对象。

但是,如果您可以从foo调用a.downcast(),您将观察到运行时多态性仍然可以从foo内部工作。(由于downcast()是非const,因此此时不可能调用)

需要dynamic_cast。你这样做是没用的。

c++允许在覆盖的函数上使用协变返回值。因为derived继承自base,所以它符合条件。但是,如果调用函数的基类版本,您仍然会得到IT返回的类型,而不是派生类的版本。要获得该返回类型,必须已经进行了类型转换,以便编译器知道函数返回的是什么类型。

所以我们可以用一种比较通用的方式来做。

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调用的可调用对象(确切的类型,包括l/r值和所需的const),因此请使列表冗长。没有隐式强制转换;这可以通过更多的工作来修复。

现在,在base中添加一个名为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);
}

具有相同的主体。

现在在main:

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);}
};

,我们写了一个apply,它接受一个visitor&,并在它上面做.invoke(this)

请注意,这种技术,特别是如果您明智地在Ts而不是要求精确匹配之间进行选择,允许通过递归实现多个分派多态性(或者,您可以使用多个类型包将dynamic_dispatch更改为dynamic_dispatch< R, types... >)。