Link to home
Start Free TrialLog in
Avatar of mrwad99
mrwad99Flag for United Kingdom of Great Britain and Northern Ireland

asked on

What does std::atomic give me?

Ah hello.  

I have asked about std::atomic in a couple of questions before, and have had some falacies I was holding corrected.  Here is the code I have used in previous questions:

class CCounter
{
public:
	CCounter(int nVal = 0) : m_nVal(nVal) {}
	int operator++(int) { int nVal = m_nVal; ++m_nVal; return nVal; }
	int operator++() { m_nVal++; return m_nVal; }
	int m_nVal;
};

Open in new window


Can someone explain what benefit I get by using a std::atomic<CCCounter> as opposed to just a normal CCounter?  I can just use the appropriate atomic inrement functions in my operator++, so what does the atomic<> template give me exactly?  I have googled for this but all the information is about the built in specialisations, nothing for custom classes.

TIA
ASKER CERTIFIED SOLUTION
Avatar of sarabande
sarabande
Flag of Luxembourg image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>> and the brilliant remarks made by rix
Thank you, Sara - that's very kind of you.
I need to modify some of my previous comment...

So, it seems that only the intrinsic types provide the full spectrum of operators for specialisation (I've only just realise this -sorry).

http://en.cppreference.com/w/cpp/atomic/atomic

There are non-member function template equivalents for all member functions of std::atomic. Those non-member functions may be additionally overloaded for types that are not specializations of std::atomic, but are able to guarantee atomicity. The only such type in the standard library is std::shared_ptr<T>.
For an atomic type of T only assignment is available, which is achieve via in-place constructor (hence the need for the class to be trivially copy constructable). The std::atomic assignment operator makes sure this is an atomic operation, which it can do because the object (and hence all the members) are trivially copy constructable. So, in that respect, you need to nothing special for assignment; by default the std:atomic class with ensure that is always atomic.

For other operations you need to implement your own free-standing versions of member functions to support the framework. For example, consider a class foo that has an addition operator. This will not be available via the atomic version by default so you will need to implement your own.

Example:

#include <atomic>

class foo
{
public:
   foo(int x = 0) noexcept
      : x_(x)
   {
   }

   foo operator + (foo rhs) const
   {
      return rhs.x_ + rhs.x_;
   }

private:
   int x_;
};

template<typename T>
foo operator +(std::atomic<T> const & lhs, T const & rhs)
{
   return lhs.load() + rhs;
}

int main()
{
   auto && a = std::atomic<foo>(foo(1));
   auto && b = a + foo(2);
}

Open in new window


Here's another example:

#include <atomic>

class foo
{
public:
   foo(int x = 0) noexcept
      : x_(x)
   {
   }

   foo & operator += (foo rhs)
   {
      x_ += rhs.x_;
      return *this;
   }

private:
   int x_;
};

template<typename T>
std::atomic<T> operator +=(std::atomic<T> & lhs, T const & rhs)
{
   return lhs.load() += rhs;
}

int main()
{
   auto && a = std::atomic<foo>(foo(1));
   a += foo(2);
}

Open in new window


And yet another:

#include <atomic>

class foo
{
public:
   foo(int x = 0) noexcept
      : x_(x)
   {
   }

   foo operator ++()
   {
      return ++x_;
   }

private:
   int x_;
};

template<typename T>
std::atomic<T> & operator ++(std::atomic<T> & x)
{
   x.store(++x.load());
   return x;
}

int main()
{
   auto && a = std::atomic<foo>(foo(1));
   ++a;
}

Open in new window


I hope that makes sense, and I apologise if my previous comment was a little misleading. I didn't realise only the integral version of std::atomic<> (such as int, long, char and pointer types and so on) had the full spectrum of class member operators available for you to specialise. In the class T version you have to do it by implementing free standing functions. Not really sure why this is the case, since all the operators could be implemented given that assignment is always atomic!
This has turned out to be a really rather interesting question. Although I've been using std::atomic for quite a while now I never really bothered to give much thought to how it actually worked. Now I've done so I realise just how elegant it is as a solution. The fact it only supports POD types now makes perfect sense to me (again, I didn't really thing it through before - heh).

It means it can do a low-level bitwise copy of the objects in a mutually exclusive memory sync'd way without needing to know anything special about the class. So, in that respect all assignments are atomic. Everything else you'd want to do with an object can be supported via free-standing operators that you overload for your atomic type.
Avatar of mrwad99

ASKER

Thanks again both :)
Avatar of mrwad99

ASKER

Sara I just typed you first example and it won't compile due to the member std::atomic<int> making the class not trivially copyable, so I'd have to use InterlockedIncrement functions instead...