For C++ (not really sure what language the OP was asking about) another simple option (but I think this one is still the simplest as long as you don't need to treat pthread_self()
as valid anywhere) would be to use std::optional<pthread_t>
(or boost's version if you can't swing C++17 or later, or implement something similar yourself).
Then use .has_value()
to check if values are valid, and be happy:
// so for example:
std::optional<pthread_t> create_thread (...) {
pthread_t thread;
if (pthread_create(&thread, ...))
return std::optional<pthread_t>();
else
return thread;
}
// then:
std::optional<pthread_t> id = create_thread(...);
if (id.has_value()) {
pthread_join(id.value(), ...);
} else {
...;
}
You still need to use pthread_equal
when comparing valid values, so all the same caveats apply. However, you can reliably compare any value to an invalid value, so stuff like this will be fine:
// the default constructed optional has no value
const std::optional<pthread_t> InvalidID;
pthread_t id1 = /* from somewhere, no concept of 'invalid'. */;
std::optional<pthread_t> id2 = /* from somewhere. */;
// all of these will still work:
if (id1 == InvalidID) { } // ok: always false
if (id1 != InvalidID) { } // ok: always true
if (id2 == InvalidID) { } // ok: true if invalid, false if not.
if (id2 != InvalidID) { } // ok: true if valud, false if not.
Btw, if you also want to give yourself some proper comparison operators, though, or if you want to make a drop-in replacement for a pthread_t
(where you don't have to call .value()
), you'll have to write your own little wrapper class to handle all the implicit conversions. It's pretty straightforward, but also getting off-topic, so I'll just drop some code here and give info in the comments if anybody asks. Here are 3 options depending on how old of a C++ you want to support. They provide implicit conversion to/from pthread_t
(can't do pthread_t*
though), so code changes should be minimal:
//-----------------------------------------------------------
// This first one is C++20 only:
#include <optional>
struct thread_id {
thread_id () =default;
thread_id (const pthread_t &t) : t_(t) { }
operator pthread_t () const { return value(); }
friend bool operator == (const thread_id &L, const thread_id &R) {
return (!L.valid() && !R.valid()) || (L.valid() && R.valid() && pthread_equal(L.value(), R.value()));
}
friend bool operator == (const pthread_t &L, const thread_id &R) { return thread_id(L) == R; }
bool valid () const { return t_.has_value(); }
void reset () { t_.reset(); }
pthread_t value () const { return t_.value(); } // throws std::bad_optional_access if !valid()
private:
std::optional<pthread_t> t_;
};
//-----------------------------------------------------------
// This works for C++17 and C++20. Adds a few more operator
// overloads that aren't needed any more in C++20:
#include <optional>
struct thread_id {
// construction / conversion
thread_id () =default;
thread_id (const pthread_t &t) : t_(t) { }
operator pthread_t () const { return value(); }
// comparisons
friend bool operator == (const thread_id &L, const thread_id &R) {
return (!L.valid() && !R.valid()) || (L.valid() && R.valid() && pthread_equal(L.value(), R.value()));
}
friend bool operator == (const thread_id &L, const pthread_t &R) {return L==thread_id(R);}
friend bool operator == (const pthread_t &L, const thread_id &R) {return thread_id(L)==R;}
friend bool operator != (const thread_id &L, const thread_id &R) {return !(L==R);}
friend bool operator != (const thread_id &L, const pthread_t &R) {return L!=thread_id(R);}
friend bool operator != (const pthread_t &L, const thread_id &R) {return thread_id(L)!=R;}
// value access
bool valid () const { return t_.has_value(); }
void reset () { t_.reset(); }
pthread_t value () const { return t_.value(); } // throws std::bad_optional_access if !valid()
private:
std::optional<pthread_t> t_;
};
//-----------------------------------------------------------
// This works for C++11, 14, 17, and 20. It replaces
// std::optional with a flag and a custom exception.
struct bad_pthread_access : public std::runtime_error {
bad_pthread_access () : std::runtime_error("value() called, but !valid()") { }
};
struct thread_id {
thread_id () : valid_(false) { }
thread_id (const pthread_t &t) : thr_(t), valid_(true) { }
operator pthread_t () const { return value(); }
friend bool operator == (const thread_id &L, const thread_id &R) {
return (!L.valid() && !R.valid()) || (L.valid() && R.valid() && pthread_equal(L.value(), R.value()));
}
friend bool operator == (const thread_id &L, const pthread_t &R) { return L==thread_id(R); }
friend bool operator == (const pthread_t &L, const thread_id &R) { return thread_id(L)==R; }
friend bool operator != (const thread_id &L, const thread_id &R) { return !(L==R); }
friend bool operator != (const thread_id &L, const pthread_t &R) { return L!=thread_id(R); }
friend bool operator != (const pthread_t &L, const thread_id &R) { return thread_id(L)!=R; }
bool valid () const { return valid_; }
void reset () { valid_ = false; }
pthread_t value () const { // throws bad_pthread_access if !valid()
if (!valid_) throw bad_pthread_access();
return thr_;
}
private:
pthread_t thr_;
bool valid_;
};
//------------------------------------------------------
/* some random notes:
- `std::optional` doesn't let you specify custom comparison
functions, which would be convenient here.
- You can't write `bool operator == (pthread_t, pthread_t)`
overloads, because they'll conflict with default operators
on systems where `pthread_t` is a primitive type.
- You have to write overloads for all the combos of
pthread_t/thread_id in <= C++17, otherwise resolution is
ambiguous with the implicit conversions.
- *Really* sloppy but thorough test: https://godbolt.org/z/GY639ovzd
pthread_t invalid_thread = pthread_self()
to aid readability. – Hulbigstd::optional<pthread_t>
is another possible option. – Hanhhank