c++ 11派生对象上基于范围的for循环

C++11 range-based for loop on derived objects

本文关键字:范围 循环 for 于范围 派生 对象 c++      更新时间:2023-10-16

如果我有一个指向父类的指针向量,并且该向量是通过实例化从父类派生的对象来初始化的,那么我似乎不能使用基于范围的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中最干净的方法吗?还有其他的选择吗?

cppreference.com页面声明基于范围的for循环表达式产生类似于以下代码(__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();
}

我们可以模板化helper类,以便在其他父派生上下文中使用它:

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,但您必须意识到它的开销。

这不是一个解决方案,我会用在我的项目,但我希望它看起来干净整洁的你;)

编辑

然而,

转换(无论是显式地使用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向量有混合派生类型,这是必要的。

我已经更新了现场演示以包含新的代码。


1虽然,正如我们在评论中看到的,有更好的方法来实现你的目标。

您应该能够使用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
        }
    }

范围迭代器在语法上更简洁,不能在for循环内强制转换。

for (Parent* p : objects) {
    Derived* d = static_cast<Derived*>(p);
    // Use d
}

如果你是向下铸造,它应该在体内完成。一个设计良好的程序并不需要向下铸造。小心!

p。S:下铸时,dynamic_cast优于static_cast。但是当您使用dynamic_cast时,RTTI会有一点成本。检查dynamic_cast?