3

When initializing an atomic class member it requires a 'deleted' function, but adding it would make it no longer trivially copyable which is a requirement for an object/struct to be atomic. Am I just not understanding how to do this correctly, or is this a problem in the c++ standard?

Take the example below:

#include <atomic>
#include <cstdint>

template<typename T>
struct A
{
    T * data;
    std::atomic<uintptr_t> next;
};

template<typename T>
class B
{
    std::atomic<A<T>> myA;

    public:
    B ( A<T>  & a ) noexcept
    { 
        myA.store(a, std::memory_order_relaxed );
    }
};  

int main ()
{
    A<int> a;
    B<int> b(a);
    return 0;
}

Trying to compile this with g++ gives error: use of deleted function 'A<int>::A(const A<int>&)' myA.store(a, std::memory_order_relaxed);. My understanding of this error is that the atomic::store method is looking for that constructor in my struct A but not finding it.

Now here is what happens when I add that constructor:

#include <atomic>
#include <cstdint>

template<typename T>
struct A
{
    T * data;
    std::atomic<uintptr_t> next;
    A(const A<T>& obj) { } 
    A( ) { } 

};

template<typename T>
class B
{
    std::atomic<A<T>> myA;

    public:
    B ( A<T>  & a ) noexcept
    { 
        myA.store(a, std::memory_order_relaxed );
    }
};  

int main ()
{
    A<int> a;
    B<int> b(a);
    return 0;
}

I no longer receive the above compiler error but a new one coming from the requirements of the atomic class required from 'class B<int>' .... error: static assertion failed: std::atomic requires a trivially copyable type ... In other words by adding the used-defined constructors I have made my struct A a non-trivially copyable object which cannot be initialized in class B. However, without the user-defined constructors I cannot use the store method in myA.store(a, std::memory_order_relaxed).

This seems like a flaw in the design of the std::atomic class. Now maybe I am just doing something wrong because I don't have a lot of experience using C++11 and up (I'm old school). Since 11 there have been a lot of changes and the requirements seem to be a lot stricter. I'm hoping someone can tell me how to achieve what I want to achieve.

Also I cannot change std::atomic<A<T>> myA; to std::atomic<A<T>> * myA; (changed to pointer) or std::atomic<A<T>*> myA;. I realize this will compile but it will destroy the fundamental design of a class I am trying to build.

annoying_squid
  • 513
  • 5
  • 10
  • Which operations on `B` do you want to be atomic, exactly? And why does it need an *atomic* `A` *value*? If this is supposed to be an ABA counter, you need union hacks and you need to check that the relevant `atomic<>` objects are lock-free for it to even hope to be safe. https://stackoverflow.com/questions/38984153/how-can-i-implement-aba-counter-with-c11-cas – Peter Cordes Dec 16 '17 at 06:13
  • And BTW: the constructor for a struct with an atomic member should pass a parameter to the `atomic` constructor, instead of using the default constructor and then doing an atomic store. See my answer on [Copy constructor for classes with atomic member](https://stackoverflow.com/questions/19961043/copy-constructor-for-classes-with-atomic-member/46045691#46045691) – Peter Cordes Dec 16 '17 at 06:19

1 Answers1

3

The problem here resides in the fact that std::atomic requires a trivially copiable type. This because trivially copyable types are the only sure types in C++ which can be directly copied by copying their memory contents directly (eg. through std::memcpy). Also non-formerly trivially copyable types could be safe to raw copy but no assumption can be made on this.

This is indeed important for std::atomic since copy on temporary values is made through std::memcpy, see some implementation details for Clang for example.

Now at the same time std::atomic is not copy constructible, and this is for reasonable reasons, check this answer for example, so it's implicitly not trivially copyable (nor any type which contains them).

If, absurdly, you would allow a std::atomic to contain another std::atomic, and the implementation of std::atomic contains a lock, how would you manage copying it atomically? How should it work?

Jack
  • 131,802
  • 30
  • 241
  • 343