如果我有一个指向父类的指针向量,并且通过实例化从父类派生的对象来初始化向量,那么看起来我不能使用基于范围的for循环以便我获得元素作为派生对象。这是一个简单的例子:
#include <vector>
class Parent {};
class Derived : public Parent {};
int main()
{
std::vector<Parent*> objects { new Derived(), new Derived() };
for (Derived* d : objects) { // ERROR
// Use d
}
return 0;
}
是否有一种干净的方式来做我想要的事情(即循环遍历派生对象)?我知道可以这样做:
for (Parent* p : objects) {
Derived* d = static_cast<Derived*>(p);
// Use d
}
但这是C ++ 11最干净的方法吗?还有什么其他选择?
答案 0 :(得分:3)
您应该可以使用dynamic_cast
来实现此目标:
class Parent {
public:
virtual ~Parent() { } // make class polymorphic
};
class Derived : public Parent {};
int main()
{
std::vector<Parent*> objects { new Derived(), new Derived() };
for (Parent* d : objects) { // ERROR
auto temp = dynamic_cast<Derived*>(d);
if (d) // cast succeeded
{
// we found the right class
}
}
答案 1 :(得分:3)
范围迭代器在语法上更清晰,你不能在for
循环中进行转换。
for (Parent* p : objects) {
Derived* d = static_cast<Derived*>(p);
// Use d
}
如果你在堕落,那么它应该在体内完成。精心设计的程序并不需要降级。小心!
P.S:在向下投射时更喜欢dynamic_cast
而不是static_cast
。但是当你使用dynamic_cast
时,RTTI会有一点费用。检查 - Performance of dynamic_cast?
答案 2 :(得分:3)
cppreference.com页面指出range-based for loop表达式生成的代码类似于以下代码(__range
,__begin
和__end
仅用于展示:
for( range_declaration : range_expression ) loop_statement
{
auto && __range = range_expression ;
for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {
range_declaration = *__begin;
loop_statement
}
}
根据该说明,我推断__begin
和__end
必须属于同一类型,事实上,这不是您的情况。指向Parent
的指针不是指向Derived
的指针,它们必须显式转换,而基于范围的for循环根本不执行任何转换。
为了实现你正在寻找的东西,我会创建一个帮助类,为我执行演员 1 ,例如:
struct Helper
{
Helper(Parent *p) : obj(p) {}
Parent *const obj;
operator Derived *() { return static_cast<Derived *>(obj); }
};
这个助手允许我们执行以下操作:
std::vector<Parent*> objects { new Derived(), new Derived() };
for (Helper h : objects) {
Derived *d = h;
d->use();
}
我们可以对辅助类进行模板化,以便在其他父源派生的上下文中使用它:
template <typename T> struct THelper
{
THelper(Parent *p) : obj(p) {}
Parent *const obj;
operator T *() { return static_cast<T *>(obj); }
};
...
...
for (THelper<Derived> h : objects) {
Derived *d = h;
d->use();
}
for (THelper<Derived2> h : objects) {
Derived *d = h;
d->use();
}
但如果您的objects
向量包含混合类型,则可能很危险。
作为改进,您可以将static_cast
替换为dynamic_cast
并检查nullptr
,但您必须了解it's overhead。
这不是我在我的项目中使用的解决方案,但我希望它看起来干净整洁;)
但是,转换(无论是
static_cast
显式还是通过Helper
类)仍然需要作为for循环中的附加行。
正如我们之前所说的那样,强制转换(静态或动态)是强制性的,但是如果您担心通过添加额外的行来截断循环的整洁性,那么operator ->
应该重载诀窍:
struct Helper
{
Helper(Parent *p) : obj(p) {}
Parent *const obj;
operator Derived *() { return static_cast<Derived *>(obj); }
Derived *operator ->() { return static_cast<Derived *>(obj); }
};
for (Helper h : objects) {
h->use();
}
请注意,如果您使用的是operator ->
,则无法检查nullptr
,如果您的objects
向量具有混合派生类型,那么这将是必要的。
我已更新live demo以包含新代码。
1 虽然我们可以阅读评论,但有更好的方法来实现您的目标。