在参加 C++ 入门课程时,我开始思考以下内容是否可能:
#include <vector>
using namespace std;
class Parent {};
class ChildA : public Parent {};
class ChildB : public Parent {};
void doSomething(vector<Parent> v){
if (v is of type vector<ChildA>) ...
else if (v is of type vector<ChildB>) ...
}
int main(void){
vector<ChildA> a;
vector<ChildB> b;
doSomething(a);
doSomething(b);
return 0;
}
简而言之,我只想定义一次的方法
doSomething()
会根据输入向量是否具有 ChildA
或 ChildB
类型的元素而表现不同。目前我面临两个问题:
Parent
作为参数的方法也允许我将 ChildA
作为参数传递,但一旦使用向量,情况就不是这样了,即我收到错误no suitable user-defined conversion from "std::vector<ChildA, std::allocator<ChildA>>" to "std::vector<Parent, std::allocator<Parent>>" exists
问题
void doSomething(vector<Parent> v){
if (v is of type vector<ChildA>) ...
else if (v is of type vector<ChildB>) ...
}
两个测试的答案都是
false
。正如声明所说,参数 v
是一个 vector<Parent>
。
你需要
template< typename Elem>
void doSomething(vector<Elem> v){
if constexpr(std::is_base_of_v<ChildA, Elem>) ...
else if constexpr(std::is_base_of_v<ChildB, Elem>) ...
}
现在您有了实际的
Elem
类型并且可以检查它。但简单的重载可能会更容易。
您可以依靠动态多态性、静态多态性或两者的混合来实现此目的。
动态多态性:
void do_something(const vector<Parent*>& vec) {
for (auto* p : vec)
p->call_virtual_function();
}
本例中的向量需要包含指向对象的(智能)指针以避免切片。
过载解析:
void overloaded_function(const Child1& ch) { }
void overloaded_function(const Child2& ch) { }
template<class Type, std::enable_if_t<std::is_base_of:v<Parent, Type>, int> = 0> // or c++20 concepts
void do_something(const vector<Type>& vec) {
for (auto& el : vec)
call_overloaded_function(el);
}
在这种情况下,向量必须是
Child1
或 Child2
类型的向量才能工作,但请注意,在这种情况下,向量不需要包含指针。这不依赖于动态多态性。
您也可以使用
dynamic_cast
但这可能被视为糟糕的代码:
void do_something_single(const Parent* p)
{
if (auto* c = dynamic_cast<const Child1*>(p)) {
// do something with c as Child1
} else if (auto* c = dynamic_cast<const Child2*>(p)) {
// do something with c as Child2
} else ...
}
void do_something(const vector<Parent*>& vec) {
for (auto*p : vec) {
do_something_single(p);
}
}
你所要求的是不可能的。
std::vector<Parent>
和std::vector<ChildA>
与std::vector<foo>
和std::vector<bar>
一样有很多共同点:几乎没有。它们是同一模板的实例化,但这意味着比您想象的要少,特别是 std::vector<ChildA>
和 std::vector<ChildB>
没有共同的基类。
您需要多态性的指针或引用。也许您实际上想要一个
std::vector<std::unique_ptr<Parent>>
可以存储指向三种类型之一的指针。
对于对象向量,您可以将函数设为函数模板:
template <typename T>
void doSomething(std::vector<T> v){ /*...*/ }
但是,如果您希望
doSomething
根据类型做不同的事情,那么这样做几乎没有什么好处,您可以简单地使用重载:
void doSomething(std::vector<ChildA>) { /*...*/ }
void doSomething(std::vector<ChildB>) { /*...*/ }
采用 Parent 作为参数的方法也允许我将 ChildA 作为参数传递,[...]
是的,但不是。方法
void foo(Parent)
仅接受 Parent
对象作为参数。如果您向其传递 ChildA
,则派生类的部分将被切掉,并且仅将 Parent
传递给函数。请参阅什么是对象切片?。您需要多态性的指针或引用。