试图用多态性来降视,出了什么问题?
Trying to downcast with polymorphism, what's going wrong?
我有两个指向基类的指针,一个指向实际的基类对象,另一个指向派生对象。我还有一个非成员函数,它为基类和派生类重载。我想使用多态性来向下转换指针,以便调用正确的重载。
代码的输出是
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类型:ptr1
和ptr2
的static类型和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>>
接受任何可以用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);
}
具有相同的主体。
现在在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... >
)。
- 警告处理为错误这里有什么问题
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 当我尝试添加 2 个大字符串时,我无法弄清楚出了什么问题
- 违反const正确性:我应该现实地期待什么问题
- 这个带有模板<类 Vector 的C++代码片段有什么问题>
- 我的逻辑反转字符串中的元音有什么问题?
- 需要以下代码的帮助,下面的代码有什么问题
- 常量公共成员有什么问题?
- 以下代码中的函数模板有什么问题?
- 这个返回元素位置的基于循环的函数有什么问题?
- creat_list2功能有什么问题?
- 格式说明符C++有什么问题
- 任何人都可以告诉我我的 C++ 代码出了什么问题?
- 从 argv[1] 转换为字符 * 字符串后有什么问题?
- 我的堆栈和库存清单程序的结构有什么问题?
- 此工厂功能有什么问题?
- 以下 C++ 代码有什么问题?
- 数组为此合并排序函数提供了正确的输出,但向量给出了不正确的输出.出了什么问题?
- reinterpret_cast,只读访问,简单的可复制类型,会出什么问题?
- 它解决了什么问题,对于非真空初始化,生命周期在初始化之前就开始了