本文介绍了使用C ++ 11基于范围的正确方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧! 问题描述 29岁程序员,3月因学历无情被辞! 使用C ++ 11基于范围的 for 的正确方法是什么? 使用? for(auto elem:container),或 for(auto& elem:container) c $ c> for(const auto& elem:container)? 或其他?解决方案让我们开始区分观察 与修改。 观察元素 让我们考虑一个简单的例子: vector< int& v = {1,3,5,7,9}; for(auto x:v) cout<< x<< ''; 上面的代码打印了元素( int : 现在考虑另一种情况,向量元素不仅仅是简单的整数,,而是一个更复杂的类的实例,具有自定义副本构造函数等。 //一个样例测试类,带有自定义复制语义。 class X { public: X():m_data(0) {} X数据):m_data(data) {} 〜X() {} X(const X& other) :m_data(other.m_data) {cout< X copy ctor.\\\; } 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; } 如果我们使用上述 v){...}这个新类的语法: vector< X> v = {1,3,5,7,9}; cout<< \\\Elements:\\\; for(auto x:v) { cout< x<< ''; } 输出如下: [...复制构造函数调用向量< X>初始化...] 元素: X copy ctor。 1 X copy ctor。 3 X copy ctor。 5 X copy ctor。 7 X copy ctor。 9 ,在基于范围的循环迭代中进行复制构造函数调用。 这是因为我们从容器中捕获元素按值 (自动x 部分在中(auto x:v))。 这是低效的代码,例如如果这些元素是 std :: string 的实例,则可以进行堆内存分配,对内存管理器的访问很昂贵。 所以,一个更好的语法是可用的:capture by const 参考,例如 const auto& : 矢量< X> v = {1,3,5,7,9}; cout<< \\\Elements:\\\; for(const auto& x:v) { cout< x<< ''; } 现在的输出是: $ b b [...复制构造函数调用向量< X>初始化...] 元素: 1 3 5 7 9 没有任何伪造的(也可能是昂贵的)复制构造函数调用。 在容器中(即对于只读访问)以下语法适用于简单的 cheap-to-copy 类型,例如 int , double 等: :container) 否则,通过 const 参考在一般情况,中更好,以避免无用的(可能是昂贵的)复制构造函数调用: for(const auto& elem:container) 修改容器中的元素 如果我们要使用基于范围的为 修改容器中的元素, 上面的为(auto elem:container)和为(const auto& elem:container) 语法错误。 事实上,在前一种情况下, elem 会存储原始文件的副本 元素,所以对它的修改只是丢失,并且不会永久存储在容器中,例如: vector< int> v = {1,3,5,7,9}; for(auto x:v)//< - capture by value(copy) x * = 10; // // *不是原始向量元素。 for(auto x:v) cout<< x<< ''; 输出只是初始序列: 1 3 5 7 9 相反,尝试使用(const auto& x:v)无法编译。 g ++会输出一个错误信息: TestRangeFor。 cpp:138:11:错误:只读引用'x'的赋值x * = 10; ^ 由非 - const 引用: 向量< 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)语法也适用于更复杂的类型, eg考虑到向量< string> : vector< string& v = {Bob,Jeff,Connie}; //修改元素:useauto& for(auto& x:v) x =Hi+ x +!; //输出元素(*观察* - >使用const auto&) for(const auto& x:v) cout< x<< ''; 输出为: 你好鲍勃!杰夫! Hi Connie! 代理迭代器的特殊情况 假设我们有一个向量< bool> ,我们要反转其元素的逻辑布尔状态,语法: 向量< bool> v = {true,false,false,true}; for(auto& x:v) x =!x; 上述代码无法编译。 g ++会输出类似如下的错误消息: TestRangeFor.cpp:168:20:error :的非常量引用的无效初始化类型为'std :: _ Bit_iterator :: referenced ce'的类型'std :: _ Bit_reference&'的无效引用无效 for(auto& x:v) ^ 问题是 std :: vector 模板 是 bool 实现包 bool 以优化空间(每个布尔值是存储在一位, 字节中的位)。 因为这样(因为无法返回对单个位的引用), 向量< bool> 使用所谓的代理迭代器模式。 代理迭代器是一个迭代器,当解除引用时,不会产生普通 bool& 而是返回(按值)临时对象,,它是 代理类转换为 bool 。 (另请参见 要修改 vector< bool> ,必须使用一种新的语法(使用 auto&&& ): for(auto&& x:v)x =!x; 以下代码工作正常: vector< bool> v = {true,false,false,true}; //反转布尔状态 for(auto& x:v)// x =!x; //打印新元素值 cout< boolalpha; for(const auto& x:v) cout<< x<< ''; 并输出: false true true false $ b b 请注意, for(auto& amp; elem:container) 语法也适用于其他情况普通(非代理)迭代器(例如对于向量 或向量< string> )。 p> (另一方面,前面提到的的语法对于代理迭代器情况。) 摘要 上述讨论可以总结在以下指南 - 行: 要观察元素,请使用以下语法: for(const auto& elem:container)//由const引用捕获 如果对象便宜复制(如 int s, double s等),可以使用稍微简化的形式: for(auto elem:container)//以值捕获 为了修改 p> for(auto& elem:container)// capture by(non-const)reference 如果容器使用代理迭代器(如 std :: vector< bool> ),请使用: for(auto&& elem:container)// capture by&& 当然,如果需要在循环体中创建元素的本地副本,请按值(自动元素:容器))是一个不错的选择。 / h2> 在通用代码中,因为我们无法假设通用类型 T 在观察模式中,可以安全地始终使用 (const auto& elem:container) br> (这将不会触发潜在的昂贵的无用副本,对于像$ int 这样的低价格到复制类型也适用,代理迭代器,如 std :: vector< bool> 。) / strong>模式,如果我们希望泛型代码在代理迭代器的情况下也有效,最好的选择是 for(auto& elem:container) 。 (这对于使用普通非代理迭代器的容器也很好用,例如 std ::向量< int> 或 std :: vector< string> 。) 中,可以提供以下指南: / strong>元素,使用: for(const auto& elem:container) 要修改元素,请使用: $ b b for(auto& elem:container) What is the correct way of using C++11's range-based for?What syntax should be used? for (auto elem : container),or for (auto& elem : container) or for (const auto& elem : container)?Or some other? 解决方案 Let's start differentiating between observing the elements in the continervs. modifying them in place.Observing the elementsLet's consider a simple example:vector<int> v = {1, 3, 5, 7, 9};for (auto x : v) cout << x << ' ';The above code prints the elements (ints) in the vector:1 3 5 7 9Now consider another case, in which the vector elements are not just simple integers,but instances of a more complex class, with custom copy constructor, etc.// 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;}If we use the above for (auto x : v) {...} syntax with this new class:vector<X> v = {1, 3, 5, 7, 9};cout << "\nElements:\n";for (auto x : v){ cout << x << ' ';}the output is something like:[... 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.9As it can be read from the output, copy constructor calls are made duringrange-based for loop iterations.This is because we are capturing the elements from the container by value(the auto x part in for (auto x : v)).This is inefficient code, e.g. if these elements are instances of std::string,heap memory allocations can be done, with expensive trips to the memory manager, etc.This is useless if we just want to observe the elements in a container.So, a better syntax is available: capture by const reference, i.e. const auto&:vector<X> v = {1, 3, 5, 7, 9};cout << "\nElements:\n";for (const auto& x : v){ cout << x << ' ';}Now the output is: [... copy constructor calls for vector<X> initialization ...]Elements:1 3 5 7 9Without any spurious (and potentially expensive) copy constructor call.So, when observing elements in a container (i.e. for a read-only access),the following syntax is fine for simple cheap-to-copy types, like int, double, etc.:for (auto elem : container)Else, capturing by const reference is better in the general case,to avoid useless (and potentially expensive) copy constructor calls:for (const auto& elem : container)Modifying the elements in the containerIf we want to modify the elements in a container using range-based for,the above for (auto elem : container) and for (const auto& elem : container)syntaxes are wrong.In fact, in the former case, elem stores a copy of the originalelement, so modifications done to it are just lost and not stored persistentlyin the container, e.g.: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 << ' ';The output is just the initial sequence:1 3 5 7 9Instead, an attempt of using for (const auto& x : v) just fails to compile.g++ outputs an error message something like this:TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x' x *= 10; ^The correct approach in this case is capturing by non-const reference:vector<int> v = {1, 3, 5, 7, 9};for (auto& x : v) x *= 10;for (auto x : v) cout << x << ' ';The output is (as expected):10 30 50 70 90This for (auto& elem : container) syntax works also for more complex types,e.g. considering a 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 << ' ';the output is:Hi Bob! Hi Jeff! Hi Connie!The special case of proxy iteratorsSuppose we have a vector<bool>, and we want to invert the logical boolean stateof its elements, using the above syntax:vector<bool> v = {true, false, false, true};for (auto& x : v) x = !x;The above code fails to compile.g++ outputs an error message similar to this:TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::reference {aka std::_Bit_reference}' for (auto& x : v) ^The problem is that std::vector template is specialized for bool, with animplementation that packs the bools to optimize space (each boolean value isstored in one bit, eight "boolean" bits in a byte).Because of that (since it's not possible to return a reference to a single bit),vector<bool> uses a so called "proxy iterator" pattern.A "proxy iterator" is an iterator that, when dereferenced, does not yield anordinary bool &, but instead returns (by value) a temporary object,which is a proxy class convertible to bool.(See also this question and related answers here on StackOverflow.)To modify in place the elements of vector<bool>, a new kind of syntax (using auto&&)must be used:for (auto&& x : v) x = !x;The following code works fine:vector<bool> v = {true, false, false, true};// Invert boolean statusfor (auto&& x : v) // <-- note use of "auto&&" for proxy iterators x = !x;// Print new element valuescout << boolalpha;for (const auto& x : v) cout << x << ' ';and outputs:false true true falseNote that the for (auto&& elem : container) syntax also works in the other casesof ordinary (non-proxy) iterators (e.g. for a vector<int> or a vector<string>).(As a side note, the aforementioned "observing" syntax of for (const auto& elem : container) works fine also for the proxy iterator case.)SummaryThe above discussion can be summarized in the following guide-lines:For observing the elements, use the following syntax:for (const auto& elem : container) // capture by const referenceIf the objects are cheap to copy (like ints, doubles, etc.),it's possible to use a slightly simplified form:for (auto elem : container) // capture by value            For modifying the elements in place, use:for (auto& elem : container) // capture by (non-const) referenceIf the container uses "proxy iterators" (like std::vector<bool>), use:for (auto&& elem : container) // capture by &&Of course, if there is a need to make a local copy of the element inside the loop body, capturing by value (for (auto elem : container)) is a good choice.Additional notes on generic codeIn generic code, since we can't make assumptions about generic type T being cheap to copy, in observing mode it's safe to always use for (const auto& elem : container).(This won't trigger potentially expensive useless copies, will work just fine also for cheap-to-copy types like int, and also for containers using proxy-iterators, like std::vector<bool>.)Moreover, in modifying mode, if we want generic code to work also in case of proxy-iterators, the best option is for (auto&& elem : container).(This will work just fine also for containers using ordinary non-proxy-iterators, like std::vector<int> or std::vector<string>.)So, in generic code, the following guidelines can be provided:For observing the elements, use:for (const auto& elem : container)For modifying the elements in place, use:for (auto&& elem : container) 这篇关于使用C ++ 11基于范围的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云!
07-23 03:37