How to implement an atomic increment of a pointer to an integer using std::atomic?
Asked Answered
B

4

13

While porting some Windows C++ code to iOS, I need to provide an implementation of Win32's long InterlockedIncrement(long *p) call. This is easy enough using the functions defined in <libkern/OSAtomic.h>.

However, I am wondering whether it's possible to write it in a OS-agnostic way using just the C++11 facility, mainly <atomic>. I came up with this, which I am not sure accomplishes what I want:

inline long InterlockedIncrement(long* p)
{
    std::atomic<long&> atomicP(*p);
    return ++atomicP;
}

Does this work? Is that good enough? The two lines are not atomic, but the increment should be atomic, which is the key here.

All the examples of use for <atomic> that I found are different, where a std::atomic<T> is defined and used directly. Here I want to use an existing long variable that the callers passes to me by address. I couldn't find such an example.

Edit: Clang 3.2 (in Xcode 4.x) fails to compile ++atomicP with the error "cannot increment value of type std::atomic<long&>" (nor atomicP += 1 either).

What would be the correct way?

Edit again: a pointer implementation compiles...

inline long InterlockedIncrement(long* p)
{
    std::atomic<long*> atomicP(p);
    return ++(*atomicP);
}

But I'm afraid this doesn't work, since I don't increment an atomic type, but the value pointed by the pointer, which is not atomic.

Brena answered 15/11, 2012 at 14:16 Comment(5)
I don't think you can have an atomic<T&>. And the pointer version is wrong (the stored pointer itself will be atomic, not the pointed-to value).Woodson
No, this won't work at all without implementation-specific things.Forefend
Maybe it's time to convert it to std::atomic<int> for the port? Considering that long has different sizes on Windows and OSX, you will probably have to do something OS specific anyway.Foreboding
Are you sure? iOS has 4-byte ints and longs, and I thought Win32 too. Then, what do you mean? Suppose I can use ints everywhere here. How would that help me?Brena
If you could use std::atomic<int> or std::atomic<long> you could use atomic_fetch_add operation to increment variables. Take a look at my answer.Barny
D
13

Your example implementation is constructing a new atomic from a pointer each time. This is not the intended use of std::atomic and I do not believe it works how you would like.

To my knowledge, the only way to do what you are looking to do (remove dependence on InterlockedIncrement in a platform independent way) is to replace all declarations for variables that you currently are calling Win32 "interlock" calls on with std::atomic versions of them. Then, you can remove the interlocked calls and use regular value semantics to modify the variable atomically. This is more readable (and more maintainable in the future), anyway.

I understand you wish to leave existing (well tested) code in place but I don't think you can in your case.

Demulsify answered 15/11, 2012 at 17:11 Comment(1)
I was afraid of that. You summarised my situation correctly. Thanks for your assessment.Brena
P
5

__atomic_add_fetch GCC extension

In GCC 4.8, the C++ standard library implements std::atomic::operator++ with the GCC built-in __atomic_add_fetch, so you could write:

inline long InterlockedIncrement(long* p)
{
    return __atomic_add_fetch(p, 1, __ATOMIC_SEQ_CST);
}

I'm not sure for clang, but there seem to be some options like __c11_atomic_fetch_add http://clang.llvm.org/docs/LanguageExtensions.html

As others mentioned, the argument p itself would have to be std::atomic for you to use only the standard library methods: converting the pointer into atomic does not help because the atomic pointer only acts on the pointer, not on what it points to.

Portraiture answered 17/6, 2015 at 7:6 Comment(3)
Also note that the GCC built-in may or may not be available in clang.Viewy
@Viewy Under what conditions are they available? clang version, build-options, etc.Portraiture
In my case it with Clang 3.7.0 on a CentOS 6 using std=c++14 and using gcc's standard library implementation. That last is probably the key bit, since I imagine that is where it is pulling the __atomic_add_fetch from. My IDE (which is running on OSX) does not recognize the function.Viewy
B
2

I believe you could use an atomic_fetch_add operation. Take a look at the example here.

Barny answered 15/11, 2012 at 14:44 Comment(4)
That needs an existing std::atomic variable.Forefend
Since he is porting the code from Windows he could take into account this and introduce atomic variables (long -> std::atomic<long>)Barny
Of course he could, but that isn't the question he is asking. The question specifically says he want to be passed long* instead of std::atomic<long>.Woodson
Hm, sorry I missed this part: Here I want to use an existing long variable that the callers passes to me by address...Barny
B
1

While this question is about C++11, it's worth providing a C++20 solution for future readers. There is now std::atomic_ref which is a standard way of accomplishing this goal.

#include <atomic>

// note: this should ideally take a reference instead of a pointer for a cleaner API
inline long InterlockedIncrement(long& p)
{
    // (1) using operator overloads with std::memory_order::seq_cst
    return ++std::atomic_ref(p);

    // (2) using an explicit memory order
    // note: ++x can be implemented as (x++ + 1)
    return std::atomic_ref(p).fetch_add(1, std::memory_order::relaxed) + 1;
}
Briareus answered 28/9, 2023 at 9:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.