试图用多态来沮丧,出了啥问题?
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。真正的原因是函数调用是在编译时解决的,而不是在运行时解决的。而ptr2
是base*
类型,base::downcast
返回base&
,所以void foo(const base& a)
被调用。
行不通。您可能希望将foo
设为虚拟成员函数,或使用访问者模式。
你认为多态性在哪里?你没有调用 virtual
函数。
可能类似于double dispatch
【参考方案1】:
您基本上是在尝试使运行时多态性影响编译时重载决议。由于显而易见的原因,这是不可能的。函数重载是一种编译时特性,这意味着在编译时根据函数参数的静态类型执行重载解析。
在您的情况下,调用哪个 foo
的选择基于 static 类型:ptr1
和 ptr2
的 static 类型以及 static 类型的 ptr1->downcast()
和 ptr2->downcast()
返回值。后者在这两种情况下都是base
类型的左值(引用)。选择foo
不涉及多态性。编译器不知道(也不关心)在运行时这些base &
引用之一实际上将引用derived
对象。
但是,如果您可以从foo
调用a.downcast()
,您会发现朗姆酒时间多态性在foo
内部仍然有效。 (此时由于downcast()
是非常量,因此无法进行此类调用)
【讨论】:
在调用派生的向下转换函数时存在多态性,但我明白你在说什么关于编译时重载解决方案。 为什么ptr2->downcast()的静态类型是base?使用虚函数时,它总是基函数的返回类型吗?我知道它无效,因为它考虑了运行时知识,但是 type_info 库中的 typeid 告诉我它返回一个派生对象。 @roro:“静态类型”是声明的类型。ptr2
在您的示例中声明为 base *
。所以ptr2->downcast()
是base::downcast()
。并且base::downcast()
声明为返回类型base &
。因此,从静态的角度来看,ptr2->downcast()
是base
类型的左值。重载决议完全基于静态类型。同时,typeid
是一个 run-time 特性——对于多态对象,它返回它们的 dynamic 类型。 typeid
告诉您 ptr2->downcast()
返回一个 base &
引用,该引用恰好绑定到 derived
对象。【参考方案2】:
你需要dynamic_cast。你正在尝试做的事情是行不通的。
C++ 允许在覆盖 函数上使用协变返回值。由于derived
继承自base
,因此它符合条件。但是,如果您调用函数的基版本,您仍然会得到 IT 返回的类型,而不是派生类的版本。要获得该返回类型,您必须已经转换,以便编译器知道函数返回什么类型。
【讨论】:
它实际上可以与boost::variant
一起使用,但这是另一回事
不清楚“你需要 dynamic_cast”可能意味着什么。有工作代码吗?
@n.m.像if (auto pD = dynamic_cast<derived*>(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<R, types<a,b,c>>
接受任何可以用a
、b
或c
调用的任何可调用对象(准确的类型,包括所需的 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&
并在上面做.invoke(this)
。
请注意,这种技术,特别是如果您巧妙地在 Ts
之间进行选择,而不是要求完全匹配,则允许通过递归实现多个调度多态性(或者,您可以使用多个类型包将 dynamic_dispatch
更改为 dynamic_dispatch< R, types... >
)。
【讨论】:
以上是关于试图用多态来沮丧,出了啥问题?的主要内容,如果未能解决你的问题,请参考以下文章