C ++ 11基于范围的派生对象循环

时间:2014-08-18 01:13:17

标签: c++ c++11

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

3 个答案:

答案 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 虽然我们可以阅读评论,但有更好的方法来实现您的目标。