如何在 C++ 中声明原子向量

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13193484/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 17:03:53  来源:igfitidea点击:

How to declare a vector of atomic in C++

c++stlc++11atomic

提问by steffen

I am intending to declare a vector of atomic variables to be used as counters in a multithreaded programme. Here is what I tried:

我打算声明一个原子变量向量,用作多线程程序中的计数器。这是我尝试过的:

#include <atomic>
#include <vector>

int main(void)
{
  std::vector<std::atomic<int>> v_a;
  std::atomic<int> a_i(1);
  v_a.push_back(a_i);
  return 0;
}

And this is the annoyingly verbose error message of gcc 4.6.3:

这是 gcc 4.6.3 令人讨厌的冗长错误信息:

In file included from /usr/include/c++/4.6/x86_64-linux-gnu/./bits/c++allocator.h:34:0,
             from /usr/include/c++/4.6/bits/allocator.h:48,
             from /usr/include/c++/4.6/vector:62,
             from test_atomic_vec.h:2,
             from test_atomic_vec.cc:1:
/usr/include/c++/4.6/ext/new_allocator.h: In member function ‘void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, const _Tp&) [with _Tp = std::atomic<int>, __gnu_cxx::new_allocator<_Tp>::pointer = std::atomic<int>*]':
/usr/include/c++/4.6/bits/stl_vector.h:830:6:   instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]'
test_atomic_vec.cc:10:20:   instantiated from here
/usr/include/c++/4.6/ext/new_allocator.h:108:9: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)'
/usr/include/c++/4.6/atomic:538:7: error: declared here
In file included from /usr/include/c++/4.6/vector:70:0,
             from test_atomic_vec.h:2,
             from test_atomic_vec.cc:1:
/usr/include/c++/4.6/bits/vector.tcc: In member function ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]':
/usr/include/c++/4.6/bits/stl_vector.h:834:4:   instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]'
test_atomic_vec.cc:10:20:   instantiated from here
/usr/include/c++/4.6/bits/vector.tcc:319:4: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)'
/usr/include/c++/4.6/atomic:538:7: error: declared here
/usr/include/c++/4.6/bits/stl_vector.h:834:4:   instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]'
test_atomic_vec.cc:10:20:   instantiated from here
/usr/include/c++/4.6/bits/vector.tcc:319:4: error: use of deleted function ‘std::atomic<int>& std::atomic<int>::operator=(const std::atomic<int>&)'
/usr/include/c++/4.6/atomic:539:15: error: declared here
In file included from /usr/include/c++/4.6/x86_64-linux-gnu/./bits/c++allocator.h:34:0,
             from /usr/include/c++/4.6/bits/allocator.h:48,
             from /usr/include/c++/4.6/vector:62,
             from test_atomic_vec.h:2,
             from test_atomic_vec.cc:1:
/usr/include/c++/4.6/ext/new_allocator.h: In member function ‘void __gnu_cxx::new_allocator<_Tp>::construct(__gnu_cxx::new_allocator<_Tp>::pointer, _Args&& ...) [with _Args = {std::atomic<int>}, _Tp = std::atomic<int>, __gnu_cxx::new_allocator<_Tp>::pointer = std::atomic<int>*]':
/usr/include/c++/4.6/bits/vector.tcc:306:4:   instantiated from ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]'
/usr/include/c++/4.6/bits/stl_vector.h:834:4:   instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]'
test_atomic_vec.cc:10:20:   instantiated from here
/usr/include/c++/4.6/ext/new_allocator.h:114:4: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)'
/usr/include/c++/4.6/atomic:538:7: error: declared here
In file included from /usr/include/c++/4.6/vector:61:0,
             from test_atomic_vec.h:2,
             from test_atomic_vec.cc:1:
/usr/include/c++/4.6/bits/stl_algobase.h: In static member function ‘static _BI2 std::__copy_move_backward<true, false, std::random_access_iterator_tag>::__copy_move_b(_BI1, _BI1, _BI2) [with _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]':
/usr/include/c++/4.6/bits/stl_algobase.h:581:18:   instantiated from ‘_BI2 std::__copy_move_backward_a(_BI1, _BI1, _BI2) [with bool _IsMove = true, _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]'
/usr/include/c++/4.6/bits/stl_algobase.h:590:34:   instantiated from ‘_BI2 std::__copy_move_backward_a2(_BI1, _BI1, _BI2) [with bool _IsMove = true, _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]'
/usr/include/c++/4.6/bits/stl_algobase.h:661:15:   instantiated from ‘_BI2 std::move_backward(_BI1, _BI1, _BI2) [with _BI1 = std::atomic<int>*, _BI2 = std::atomic<int>*]'
/usr/include/c++/4.6/bits/vector.tcc:313:4:   instantiated from ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]'
/usr/include/c++/4.6/bits/stl_vector.h:834:4:   instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]'
test_atomic_vec.cc:10:20:   instantiated from here
/usr/include/c++/4.6/bits/stl_algobase.h:546:6: error: use of deleted function ‘std::atomic<int>& std::atomic<int>::operator=(const std::atomic<int>&)'
/usr/include/c++/4.6/atomic:539:15: error: declared here
In file included from /usr/include/c++/4.6/vector:63:0,
             from test_atomic_vec.h:2,
             from test_atomic_vec.cc:1:
/usr/include/c++/4.6/bits/stl_construct.h: In function ‘void std::_Construct(_T1*, _Args&& ...) [with _T1 = std::atomic<int>, _Args = {std::atomic<int>}]':
/usr/include/c++/4.6/bits/stl_uninitialized.h:77:3:   instantiated from ‘static _ForwardIterator std::__uninitialized_copy<_TrivialValueTypes>::__uninit_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<std::atomic<int>*>, _ForwardIterator = std::atomic<int>*, bool _TrivialValueTypes = false]'
/usr/include/c++/4.6/bits/stl_uninitialized.h:119:41:   instantiated from ‘_ForwardIterator std::uninitialized_copy(_InputIterator, _InputIterator, _ForwardIterator) [with _InputIterator = std::move_iterator<std::atomic<int>*>, _ForwardIterator = std::atomic<int>*]'
/usr/include/c++/4.6/bits/stl_uninitialized.h:259:63:   instantiated from ‘_ForwardIterator std::__uninitialized_copy_a(_InputIterator, _InputIterator, _ForwardIterator, std::allocator<_Tp>&) [with _InputIterator = std::move_iterator<std::atomic<int>*>, _ForwardIterator = std::atomic<int>*, _Tp = std::atomic<int>]'
/usr/include/c++/4.6/bits/stl_uninitialized.h:269:24:   instantiated from ‘_ForwardIterator std::__uninitialized_move_a(_InputIterator, _InputIterator, _ForwardIterator, _Allocator&) [with _InputIterator = std::atomic<int>*, _ForwardIterator = std::atomic<int>*, _Allocator = std::allocator<std::atomic<int> >]'
/usr/include/c++/4.6/bits/vector.tcc:343:8:   instantiated from ‘void std::vector<_Tp, _Alloc>::_M_insert_aux(std::vector<_Tp, _Alloc>::iterator, _Args&& ...) [with _Args = {const std::atomic<int>&}, _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::iterator = __gnu_cxx::__normal_iterator<std::atomic<int>*, std::vector<std::atomic<int> > >, typename std::_Vector_base<_Tp, _Alloc>::_Tp_alloc_type::pointer = std::atomic<int>*]'
/usr/include/c++/4.6/bits/stl_vector.h:834:4:   instantiated from ‘void std::vector<_Tp, _Alloc>::push_back(const value_type&) [with _Tp = std::atomic<int>, _Alloc = std::allocator<std::atomic<int> >, std::vector<_Tp, _Alloc>::value_type = std::atomic<int>]'
test_atomic_vec.cc:10:20:   instantiated from here
/usr/include/c++/4.6/bits/stl_construct.h:76:7: error: use of deleted function ‘std::atomic<int>::atomic(const std::atomic<int>&)'
/usr/include/c++/4.6/atomic:538:7: error: declared here

How do I solve this?

我该如何解决这个问题?

The error disappears when I comment out the line with push_back().

当我用push_back().

edit:I edited the post... For those of you who saw the first post, the error was embarrassingly that I used gccinstead of g++:\

编辑:我编辑了帖子......对于那些看到第一篇帖子的人来说,错误是令人尴尬的,我用它gcc代替了g++:\

采纳答案by jogojapan

As described in this closely related questionthat was mentioned in the comments, std::atomic<T>isn't copy-constructible, nor copy-assignable.

正如评论中提到的这个密切相关的问题所描述的,std::atomic<T>不是可复制构造的,也不是可复制分配的。

Object types that don't have these properties cannot be used as elements of std::vector.

不具有这些属性的对象类型不能用作std::vector.

However, it should be possible to create a wrapper around the std::atomic<T>element that is copy-constructible and copy-assignable. It will have to use the load()and store()member functions of std::atomic<T>to provide construction and assignment (this is the idea described by the accepted answer to the question mentioned above):

但是,应该可以在std::atomic<T>元素周围创建一个可复制构造和可复制分配的包装器。它必须使用load()store()成员函数std::atomic<T>来提供构造和赋值(这是上述问题的公认答案所描述的想法):

#include <atomic>
#include <vector>

template <typename T>
struct atomwrapper
{
  std::atomic<T> _a;

  atomwrapper()
    :_a()
  {}

  atomwrapper(const std::atomic<T> &a)
    :_a(a.load())
  {}

  atomwrapper(const atomwrapper &other)
    :_a(other._a.load())
  {}

  atomwrapper &operator=(const atomwrapper &other)
  {
    _a.store(other._a.load());
  }
};

int main(void)
{
  std::vector<atomwrapper<int>> v_a;
  std::atomic<int> a_i(1);
  v_a.push_back(a_i);
  return 0;
}

EDIT: As pointed out correctly by Bo Persson, the copy operation performed by the wrapper is not atomic. It enables you to copy atomic objects, but the copy itself isn't atomic. This means any concurrent access to the atomics must not make use of the copy operation. This implies that operations on the vector itself (e.g. adding or removing elements) must not be performed concurrently.

编辑:正如 Bo Persson 正确指出的那样,包装器执行的复制操作不是原子的。它使您能够复制原子对象,但副本本身不是原子的。这意味着对原子的任何并发访问都不得使用复制操作。这意味着不能同时执行对向量本身的操作(例如添加或删除元素)。

Example: If, say, one thread modifies the value stored in one of the atomics while another thread adds new elements to the vector, a vector reallocation may occur and the object the first thread modifies may be copied from one place in the vector to another. In that case there would be a data race between the element access performed by the first thread and the copy operation triggered by the second.

示例:假设,如果一个线程修改存储在其中一个原子中的值,而另一个线程向向量添加新元素,则可能会发生向量重新分配,并且第一个线程修改的对象可能会从向量中的一个位置复制到另一个位置. 在这种情况下,第一个线程执行的元素访问和第二个线程触发的复制操作之间将发生数据竞争。

回答by BeeOnRope

To first answer your headline question: you can declarea std::vector<std::atomic<...>>easily, as you have done in your example.

首先回答您的标题问题:您可以轻松地声明a std::vector<std::atomic<...>>,就像您在示例中所做的那样。

Because of the lack of copy or move constructors for std::atomic<>objects, however, your use of the vectorwill be restricted as you found out with the compilation error on push_back(). Basically you can't do anything that would invoke either constructor.

但是,由于缺少std::atomic<>对象的复制或移动构造函数,vector当您发现 上的编译错误时,您对 的使用将受到限制push_back()。基本上你不能做任何会调用任何一个构造函数的事情。

This means your vector's size must be fixed at construction, and you should manipulate elements using operator[]or .at(). For your example code, the following works1:

这意味着您的向量的大小必须在构造时固定,并且您应该使用operator[]或操作元素.at()。对于您的示例代码,以下工作1

std::vector<std::atomic<int>> v_a(1);
std::atomic<int> a_i(1); 
v_a[0] = a_i;

If the "fixed size at construction" limitation is too onerous, you can use std::dequeinstead. This lets you emplaceobjects, growing the structure dynamically without requiring copy or move constructors, e.g.:

如果“构建时固定大小”限制太繁重,您可以std::deque改用。这使您可以放置对象,动态增长结构,而无需复制或移动构造函数,例如:

std::deque<std::atomic<int>> d;

d.emplace_back(1);
d.emplace_back(2);
d.pop_back();

There are still some limitations, however. For example, you can pop_back(), but you cannot use the more general erase(). The limitations make sense: an erase()in the middle of the blocks of contiguous storage used by std::dequein general requires the movement of elements, hence requires copy/move constructor or assignment operators to be present.

但是,仍然存在一些限制。例如,您可以pop_back(),但不能使用更通用的erase()。限制是有道理的:通常erase()使用的连续存储块的中间std::deque需要元素的移动,因此需要存在复制/移动构造函数或赋值运算符。

If you can't live with those limitations, you could create a wrapper class as suggested in other answers but be aware of the underlying implementation: it makes little senseto move a std::atomic<>object once it is being used: it would break any threads concurrently accessing the objects. The only sane use of copy/move constructors is generally in the initial setup of collections of these objects before they are published to other threads.

如果你不能活的那些限制,你可以在其他的答案建议,但要注意的底层实现创建一个包装类:它使没有多大意义,以移动std::atomic<>一旦正在使用的对象:它会破坏任何线程同时访问对象。复制/移动构造函数的唯一合理使用通常是在这些对象的集合被发布到其他线程之前的初始设置中。



1Unless, perhaps, you use Intel's icccompiler, which fails with an internal errorwhile compiling this code.

1除非您使用英特尔的icc编译器,否则在编译此代码时会因内部错误失败

回答by Kaz Dragon

Looks to me like atomic<T>has no copy constructor. Nor a move constructor, as far as I can tell.

在我看来atomic<T>没有复制构造函数。据我所知,也不是移动构造函数。

One work around might be to use vector<T>::emplace_back()to construct the atomic in-place in the vector. Alas, I don't have a C++11 compiler on me right now, or I'd go and test it.

一种解决方法可能是使用vector<T>::emplace_back()在向量中就地构造原子。唉,我现在没有 C++11 编译器,否则我会去测试它。

回答by codesniffer

As others have properly noted, the cause of the compiler's error is that std::atomic explicitly prohibits the copy constructor.

正如其他人正确指出的那样,编译器错误的原因是 std::atomic 明确禁止复制构造函数。

I had a use case where I wanted the convenience of an STL map (specifically I was using a map of maps in order to achieve a sparse 2-dimensional matrix of atomics so I can do something like int val = my_map[10][5]). In my case there would be only one instance of this map in the program, so it wouldn't be copied, and even better I can initialize the entire thing using braced initialization. So it was very unfortunate that while my code would never attempt copying individual elements or the map itself, I was prevented from using an STL container.

我有一个用例,我想要 STL 映射的便利(具体来说,我使用映射映射来实现原子的稀疏二维矩阵,因此我可以做类似的事情int val = my_map[10][5])。在我的例子中,程序中只有这个映射的一个实例,所以它不会被复制,甚至更好的是我可以使用支撑初始化来初始化整个事物。所以非常不幸的是,虽然我的代码永远不会尝试复制单个元素或地图本身,但我被阻止使用 STL 容器。

The workaround I ultimately went with is to store the std::atomic inside a std::shared_ptr. This has pros, but possibly a con:

我最终采用的解决方法是将 std::atomic 存储在 std::shared_ptr 中。这有优点,但可能有缺点:

Pros:

优点:

  • Can store std::atomic inside any STL container
  • Does not require/restrict using only certain methods of STL containers.
  • 可以将 std::atomic 存储在任何 STL 容器中
  • 不要求/限制仅使用 STL 容器的某些方法。

Pro or Con (this aspect's desirability depends on the programs' use cases): - There is only a single sharedatomic for a given element. So copying the shared_ptr or the STL container will still yield a single shared atomic for the element. In other words, if you copy the STL container and modify one of the atomic elements, the other container's corresponding atomic element will also reflect the new value.

赞成或反对(这方面的可取性取决于程序的用例): -给定元素只有一个共享原子。因此,复制 shared_ptr 或 STL 容器仍将为元素生成单个共享原子。换句话说,如果您复制 STL 容器并修改其中一个原子元素,则另一个容器对应的原子元素也将反映新值。

In my case the Pro/Con characteristic was moot due to my use case.

就我而言,由于我的用例,Pro/Con 特性没有实际意义。

Here's a brief syntax to initialize a std::vector with this method:

这是使用此方法初始化 std::vector 的简短语法:

#include <atomic>
#include <memory>
#include <vector>

std::vector<std::shared_ptr<std::atomic<int> > > vecAtomicInts
{
    std::shared_ptr<std::atomic<int> >(new std::atomic<int>(1) ),
    std::shared_ptr<std::atomic<int> >(new std::atomic<int>(2) ),
};

// push_back, emplace, etc all supported
vecAtomicInts.push_back(std::shared_ptr<std::atomic<int> >(new std::atomic<int>(3) ) );

// operate atomically on element
vecAtomicInts[1]->exchange(4);

// access random element
int i = *(vecAtomicInts[1]);