试图用多态来沮丧,出了啥问题?

Posted

技术标签:

【中文标题】试图用多态来沮丧,出了啥问题?【英文标题】:Trying to downcast with polymorphism, what's going wrong?试图用多态来沮丧,出了什么问题? 【发布时间】:2016-11-09 16:37:42 【问题描述】:

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

代码的输出是

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 添加到向下转换函数中以说明函数覆盖/多态性。

【问题讨论】:

@tkausl - 它绝对应该覆盖基础版本。 OP 遇到的问题不是它们隐藏/重载而不是覆盖......而是编译器看到函数的基版本及其返回类型。 @tkausl 是的,它确实覆盖了它。对您来说,演示是here。真正的原因是函数调用是在编译时解决的,而不是在运行时解决的。而ptr2base* 类型,base::downcast 返回base&amp;,所以void foo(const base&amp; a) 被调用。 行不通。您可能希望将foo 设为虚拟成员函数,或使用访问者模式。 你认为多态性在哪里?你没有调用 virtual 函数。 可能类似于double dispatch 【参考方案1】:

您基本上是在尝试使运行时多态性影响编译时重载决议。由于显而易见的原因,这是不可能的。函数重载是一种编译时特性,这意味着在编译时根据函数参数的静态类型执行重载解析。

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

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

【讨论】:

在调用派生的向下转换函数时存在多态性,但我明白你在说什么关于编译时重载解决方案。 为什么ptr2->downcast()的静态类型是base?使用虚函数时,它总是基函数的返回类型吗?我知道它无效,因为它考虑了运行时知识,但是 type_info 库中的 typeid 告诉我它返回一个派生对象。 @roro:“静态类型”是声明的类型。 ptr2 在您的示例中声明为 base *。所以ptr2-&gt;downcast()base::downcast()。并且base::downcast() 声明为返回类型base &amp;。因此,从静态的角度来看,ptr2-&gt;downcast()base 类型的左值。重载决议完全基于静态类型。同时,typeid 是一个 run-time 特性——对于多态对象,它返回它们的 dynamic 类型。 typeid 告诉您 ptr2-&gt;downcast() 返回一个 base &amp; 引用,该引用恰好绑定到 derived 对象。【参考方案2】:

你需要dynamic_cast。你正在尝试做的事情是行不通的。

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

【讨论】:

它实际上可以与 boost::variant 一起使用,但这是另一回事 不清楚“你需要 dynamic_cast”可能意味着什么。有工作代码吗? @n.m.像if (auto pD = dynamic_cast&lt;derived*&gt;(ptr2)) foo(pD); else foo(ptr2); 这样的东西。 (但我不会把它写成这样的单行)【参考方案3】:

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

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&lt;R, types&lt;a,b,c&gt;&gt; 接受任何可以用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);

具有相同的身体。

现在主要:

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

live example.

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

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&amp;并在上面做.invoke(this)

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

【讨论】:

以上是关于试图用多态来沮丧,出了啥问题?的主要内容,如果未能解决你的问题,请参考以下文章

用多态替换条件

C++静态多态与动态多态的实现原理剖析

C++静态多态与动态多态的实现原理剖析

试图从多态函数中获得不同的输出

UIWebView - 抛出了啥 NSError?

附加到 C++ 中的多态列表?