How to declare a vector of atomic in C++

后端 未结 4 1856
暗喜
暗喜 2020-11-27 16:01

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 
#in         


        
相关标签:
4条回答
  • 2020-11-27 16:36

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

    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.

    0 讨论(0)
  • 2020-11-27 16:40

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

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

    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:

    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::deque instead. This lets you emplace objects, growing the structure dynamically without requiring copy or move constructors, e.g.:

    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::deque in general requires the movement of elements, hence requires copy/move constructor or assignment operators to be present.

    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 sense to 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.


    1 Unless, perhaps, you use Intel's icc compiler, which fails with an internal error while compiling this code.

    0 讨论(0)
  • 2020-11-27 16:53

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

    Object types that don't have these properties cannot be used as elements of 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):

    #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.

    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.

    0 讨论(0)
  • 2020-11-27 16:55

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

    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.

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

    Pros:

    • Can store std::atomic inside any STL container
    • Does not require/restrict using only certain methods of STL containers.

    Pro or Con (this aspect's desirability depends on the programs' use cases): - There is only a single shared atomic 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.

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

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

    #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]);
    
    0 讨论(0)
提交回复
热议问题