0
\$\begingroup\$

I was trying to implement a boolean that can be atomically flipped. The suggestion on Stack Overflow is to use an integer. This would be my implementation.

#include <atomic>
#include <iostream>


class flippable_atomic_bool {
    std::atomic<int> state;
    public:
    flippable_atomic_bool() : state(0){}
    explicit flippable_atomic_bool(bool init) : state(static_cast<int>(init)) {}
    explicit flippable_atomic_bool(const flippable_atomic_bool& other) = default;
    flippable_atomic_bool& operator =(const flippable_atomic_bool& other) = default;
    void flip() {
        state ^= static_cast<int>(true);
    }
    operator bool() const {
        return static_cast<bool>(state);
    }
};


int main() {
    {
        flippable_atomic_bool a(true);
        std::cout << static_cast<bool>(a) << std::endl;
        a.flip();
        std::cout << static_cast<bool>(a) << std::endl;
    }
    {
        flippable_atomic_bool a{false};
        std::cout << static_cast<bool>(a) << std::endl;
        a.flip();
        std::cout << static_cast<bool>(a) << std::endl;
    }
}

Outputs

1
0
0
1
\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

The default constructor might be surprising to those familiar with std::atomic<T> in C++17, which default-constructs to an uninitialised state, rather than to a default T. That said, this behaviour is correct for C++20, so a good choice!

There's no benefit to explicit copy-constructor - that can never be an implicit conversion.

The defaulted copy constructor and assignment are misleading - since state is not copyable, this causes them to be deleted, so we should write = delete instead of = default or (better) just omit them.

It's interesting that we write a literal 0 as initializer for state, but then use static_cast<int>(true) in the ^= implementation.

Since bool and int are interconvertible, we can write the whole thing without casts (I compiled with g++ -Wconversion -Wuseless-cast, amongst other options):


class flippable_atomic_bool
{
    std::atomic<int> state;
public:
    flippable_atomic_bool()
        : state{}
    {}
    explicit flippable_atomic_bool(bool init)
        : state{init}
    {}

    void flip()
    {
        state ^= 1;
    }
    operator bool() const
    {
        return state;
    }
};

The test program doesn't demonstrate much of relevance. I would expect to show several threads toggling the atomic a few thousand times each, and demonstrate that the end state is consistent.

It's not hard to do that, if we build using OpenMP:

#include <iomanip>
#include <iostream>
int main()
{
    auto a = flippable_atomic_bool{false};

#pragma omp parallel for num_threads(5)
    for (long i = 0;   i < 100000001;  ++i) {
        a.flip();
    }

    std::cout << std::boolalpha << a << '\n';
    return !a;
}

We could gain more confidence by running that race a few hundred times, and checking we always get the same result.

\$\endgroup\$
4
  • \$\begingroup\$ Thanks! I was aware the casts were not really necessary, but I thought they would make the intent more explicit. I was thinking, would it make sense to use int_fast8_t in place of int? \$\endgroup\$
    – Antonio
    Commented Sep 14, 2022 at 20:43
  • \$\begingroup\$ I was also thinking that the explicit for flippable_atomic_bool(bool init) doesn't help me much, and in fact it prevents me from doing copy initialization. What do I lose by removing it? \$\endgroup\$
    – Antonio
    Commented Sep 14, 2022 at 21:57
  • 1
    \$\begingroup\$ Since the copy constructor is defaulted, which makes it deleted, then you lose nothing by removing the explicit - it still can't be called! (If you think you're using it, it's likely you're going via the conversion-to-bool operator without realising it. And that's fine.) \$\endgroup\$ Commented Sep 15, 2022 at 6:50
  • 1
    \$\begingroup\$ If we change to std::int_fast8_t, we might need explicit casts in some places for platforms where that's narrower than int. int is supposed to be the architectures "natural type" for arithmetic, so my inclination is to stick with that. \$\endgroup\$ Commented Sep 15, 2022 at 6:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.