使用C++11的范围的正确方法是什么?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (13)

什么是使用C ++ 11的基于范围的正确方法for

应该使用什么语法?for (auto elem : container)for (auto& elem : container)or for (const auto& elem : container)?还是其他的?

提问于
用户回答回答于

让我们开始区分观察在continer元素与修改的地方他们。

观察元素

我们来看一个简单的例子:

vector<int> v = {1, 3, 5, 7, 9};

for (auto x : v)
    cout << x << ' ';

上面的代码打印元件(int在S) vector

1 3 5 7 9

现在考虑另一种情况,其中向量元素不仅仅是简单的整数,而是更复杂的类的实例,还有定制的拷贝构造函数等。

// A sample test class, with custom copy semantics.
class X
{
public:
    X() 
        : m_data(0) 
    {}

    X(int data)
        : m_data(data)
    {}

    ~X() 
    {}

    X(const X& other) 
        : m_data(other.m_data)
    { cout << "X copy ctor.\n"; }

    X& operator=(const X& other)
    {
        m_data = other.m_data;       
        cout << "X copy assign.\n";
        return *this;
    }

    int Get() const
    {
        return m_data;
    }

private:
    int m_data;
};

ostream& operator<<(ostream& os, const X& x)
{
    os << x.Get();
    return os;
}

如果我们对for (auto x : v) {...}这个新类使用上面的语法:

vector<X> v = {1, 3, 5, 7, 9};

cout << "\nElements:\n";
for (auto x : v)
{
    cout << x << ' ';
}

输出结果如下所示:

[... copy constructor calls for vector<X> initialization ...] Elements: X copy ctor. 1 X copy ctor. 3 X copy ctor. 5 X copy ctor. 7 X copy ctor. 9

由于它可以从输出中读取,复制构造函数调用是在基于范围的循环迭代过程中进行的。 这是因为我们正在捕获从容器中的元素由值 (的auto x部分for (auto x : v))。

这是低效率的代码,例如,如果这些元素是实例std::string,堆内存分配可以完成,而且内存管理器的代价高昂,如果我们只想观察容器中的元素,这是没有用的。

因此,可以使用更好的语法:通过const引用捕获,即const auto&

vector<X> v = {1, 3, 5, 7, 9};

cout << "\nElements:\n";
for (const auto& x : v)
{ 
    cout << x << ' ';
}

现在输出结果是:

[... copy constructor calls for vector<X> initialization ...] Elements: 1 3 5 7 9

没有任何虚假(并且可能是昂贵的)复制构造函数调用。

所以,当观察到在一个容器(即用于只读访问)的元素,下面的语法是对简单低价到副本类型,如intdouble等:

for (auto elem : container) 

否则,const一般情况下,通过引用进行捕获会更好,以避免无用(并且可能是代价高昂)的拷贝构造函数调用:

for (const auto& elem : container) 

修改容器中的元素

如果我们想使用基于范围的方式来修改容器中的元素for,上述for (auto elem : container)for (const auto& elem : container) 语法都是错误的。

实际上,在前一种情况下,elem存储原始元素的副本,因此对其进行的修改只会丢失并且不会永久存储在容器中,例如:

vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)  // <-- capture by value (copy)
    x *= 10;      // <-- a local temporary copy ("x") is modified,
                  //     *not* the original vector element.

for (auto x : v)
    cout << x << ' ';

输出只是初始序列:

1 3 5 7 9

相反,尝试使用for (const auto& x : v)只是不能编译。

g ++输出一个如下所示的错误信息:

TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x' x *= 10; ^

在这种情况下,正确的方法是通过非const引用捕获:

vector<int> v = {1, 3, 5, 7, 9};
for (auto& x : v)
    x *= 10;

for (auto x : v)
    cout << x << ' ';

输出(如预期):

10 30 50 70 90

这种for (auto& elem : container)语法也适用于更复杂的类型,例如考虑vector<string>

vector<string> v = {"Bob", "Jeff", "Connie"};

// Modify elements in place: use "auto &"
for (auto& x : v)
    x = "Hi " + x + "!";

// Output elements (*observing* --> use "const auto&")
for (const auto& x : v)
    cout << x << ' ';

输出是:

Hi Bob! Hi Jeff! Hi Connie!

代理迭代器的特例

假设我们有一个vector<bool>,并且我们想使用上面的语法反转它的元素的逻辑布尔状态:

vector<bool> v = {true, false, false, true};
for (auto& x : v)
    x = !x;

上面的代码无法编译。

g ++输出类似如下的错误信息:

TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::referen ce {aka std::_Bit_reference}' for (auto& x : v) ^

的问题是,std::vector模板专门bool,与该一个实现bools到优化空间(每个布尔值被存储在一个比特,一个字节八“布尔”比特)。

由于这个原因(因为不可能返回一个位的引用), vector<bool>所以使用了所谓的“代理迭代器”模式。A“代理迭代器”是一个迭代的是,解除引用的时候,也不会产生一个普通的bool &,而是返回(由值)临时对象,它是一个代理类转化成bool

为了修改元素vector<bool>auto&&必须使用一种新的语法(使用):

for (auto&& x : v)
    x = !x;

以下代码正常工作:

vector<bool> v = {true, false, false, true};

// Invert boolean status
for (auto&& x : v)  // <-- note use of "auto&&" for proxy iterators
    x = !x;

// Print new element values
cout << boolalpha;        
for (const auto& x : v)
    cout << x << ' ';

并输出:

false true true false

请注意,该for (auto&& elem : container)语法也适用于其他普通(非代理)迭代器(例如a vector<int>或a vector<string>)的情况。

(作为一个附注,前面提到的“观察”语法for (const auto& elem : container)也适用于代理迭代器。)

概要

上述讨论可以总结为以下指导原则:

  1. 观察元素,请使用以下语法: for (const auto& elem : container) // capture by const reference
    • 如果对象复制便宜(如ints,doubles等),则可以使用稍微简化的形式: for (auto elem : container) // capture by value

  2. 修改元素,请使用: for (auto& elem : container) // capture by (non-const) reference
    • 如果容器使用“代理迭代器”(如std::vector<bool>),请使用: for (auto&& elem : container) // capture by &&

当然,如果需要在循环体内创建元素的本地副本,则通过valuefor (auto elem : container))捕获是一个不错的选择。

有关通用代码的其他说明

通用代码中,由于我们无法对通用类型T进行复制的便宜假设,因此在观察模式下总是可以安全使用for (const auto& elem : container)。 (这不会触发潜在的昂贵的无用副本,对于廉价的复制类型int,以及使用代理迭代器的容器,也可以工作得很好std::vector<bool>)。

而且,在修改模式中,如果我们希望通用代码也可以在代理迭代器的情况下工作,那么最好的选择是for (auto&& elem : container)。 (对于使用普通非代理迭代器的容器,这也可以正常工作,例如std::vector<int>or)std::vector<string>

因此,在通用代码中,可以提供以下准则:

  1. 对于观察的元素,使用方法: for (const auto& elem : container)
  2. 修改元素,请使用: for (auto&& elem : container)
用户回答回答于

没有正确的方法来使用for (auto elem : container)for (auto& elem : container)for (const auto& elem : container)。你只是表达你想要的。

让我详细说明一下。。

for (auto elem : container) ...

这是一个语法:

for(auto it = container.begin(); it != container.end(); ++it) {

    // Observe that this is a copy by value.
    auto elem = *it;

}

如果你的容器包含便于复制的元素,则可以使用此容器。

for (auto& elem : container) ...

这是一个语法:

for(auto it = container.begin(); it != container.end(); ++it) {

    // Now you're directly modifying the elements
    // because elem is an lvalue reference
    auto& elem = *it;

}

例如,当你想要直接写入容器中的元素时,请使用它。

for (const auto& elem : container) ...

这是一个语法:

for(auto it = container.begin(); it != container.end(); ++it) {

    // You just want to read stuff, no modification
    const auto& elem = *it;

}

扫码关注云+社区