61

I am reading through Anthony Williams' "C++ Concurrency in Action" and in Chapter 5, which talks about the new multithreading-aware memory model and atomic operations, and he states:

In order to use std::atomic<UDT> for some user-defined UDT, this type must have a trivial copy assignment operator.

As I understand it, this means that we can use std::atomic<UDT> if the following returns true:

std::is_trivially_copyable<UDT>::value

By this logic, we shouldn't be able to use std::string as a template argument for std::atomic and have it work correctly.

However, the following code compiles and runs with expected output:

#include <atomic>
#include <thread>
#include <iostream>
#include <string>

int main()
{
    std::atomic<std::string> atomicString;

    atomicString.store( "TestString1" );

    std::cout << atomicString.load() << std::endl;

    atomicString.store( "TestString2" );

    std::cout << atomicString.load() << std::endl;

    return 0;
}

Is this a case of undefined behaviour which just happens to behave as expected?

Thanks in advance!

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Thomas Russell
  • 5,870
  • 4
  • 33
  • 68
  • 3
    What is your compiler (and your implementation of the stdlib)? I couldn't make it compiler [here](http://coliru.stacked-crooked.com/view?id=0ce3b66093e9a0a59d5179429373eea7-e54ee7a04e4b807da0930236d4cc94dc), and actually that's what I was expecting – Andy Prowl Jun 01 '13 at 19:30
  • @AndyProwl I'm using VS 2012 at the moment, without the November CTP. – Thomas Russell Jun 01 '13 at 19:31
  • 2
    As you're using it, I wouldn't expect to see a problem. The problem would arise when two (or more) threads were attempting to modify the same string at the same time. At that point, `string`'s non-trivial operator would start to cause a problem. Just wrapping something in `std::atomic` isn't likely to break code that would be fine without it. At the same time, without following its rules, it's not going to help code that would be broken without it. – Jerry Coffin Jun 01 '13 at 19:33
  • 4
    This is almost definitely "fortuitous" undefined behavior: Microsoft's string implementation uses the small string optimization, with which the small strings you are testing are effectively trivially copyable. If you use a much longer string - so that heap allocation kicks in - you should see much more colorful effects. – Casey Jun 02 '13 at 06:14
  • 2
    Of course it doesn't fail for you, you only have one thread! The UB will happen if you have multiple threads accessing the string, whereby the modifications might not be as "atomic" as you would like. (UB!) – CashCow Feb 18 '15 at 16:21
  • 2
    A normal `std::string` has at least 3 pointer-sized members so it's never going to be lock_free on any mainstream C++ implementation. It's basically pointless to let std::atomic do locking around every access instead of just using your own mutex. – Peter Cordes Nov 16 '19 at 05:08
  • @PeterCordes, maybe string_view – Sergei Krivonos Jan 23 '21 at 04:03

2 Answers2

68

The standard does not specify a specialization of std::atomic<std::string>, so the generic template <typename T> std::atomic<T> applies. 29.5 [atomics.types.generic] p1 states:

There is a generic class template atomic. The type of the template argument T shall be trivially copyable (3.9).

There is no statement that the implementation must diagnose violations of this requirement. So either (a) your use of std::atomic<std::string> invokes undefined behavior, or (b) your implementation provides std::atomic<std::string> as a conforming extension.

Looking at the MSDN page for std::atomic<T> (http://msdn.microsoft.com/en-us/library/vstudio/hh874651.aspx), it does explicitly mention the requirement that T be trivially copyable, and it does NOT say anything specific about std::atomic<std::string>. If it is an extension, it's undocumented. My money is on undefined behavior.

Specifically, 17.6.4.8/1 applies (with thanks to Daniel Krügler for setting me straight):

In certain cases (replacement functions, handler functions, operations on types used to instantiate standard library template components), the C++ standard library depends on components supplied by a C++ program. If these components do not meet their requirements, the Standard places no requirements on the implementation.

std::string certainly does not meet the std::atomic<T> requirement that the template parameter T be trivially copyable, so the standard places no requirements on the implementation. As a quality of implementation issue, note that static_assert(std::is_trivially_copyable<T>::value, "std::atomic<T> requires T to be trivially copyable"); is an easy diagnostic to catch this violation.


2016-04-19 Update: I don't know when the change happened, but VS2015 Update 2 does now diagnose std::atomic<std::string>:

error C2338: atomic requires T to be trivially copyable.
Casey
  • 41,449
  • 7
  • 95
  • 125
17

No, this is undefined behavior. Moreover, since std::string is not trivially copyable, conforming compiler should have issued "at least one diagnostic message":

29.5 Atomic types

There is a generic class template atomic. The type of the template argument T shall be trivially copyable (3.9).

1.4 Implementation compliance

— If a program contains a violation of any diagnosable rule [...] a conforming implementation shall issue at least one diagnostic message.

user3228152
  • 179
  • 1
  • 2