diff --git a/stl/inc/condition_variable b/stl/inc/condition_variable index 3d50fc860..e4304da8d 100644 --- a/stl/inc/condition_variable +++ b/stl/inc/condition_variable @@ -160,7 +160,7 @@ private: struct _Cv_any_notify_all { condition_variable_any* _This; - explicit _Cv_any_notify_all(condition_variable_any* _This_) : _This{_This_} {} + explicit _Cv_any_notify_all(condition_variable_any* _This_) noexcept : _This{_This_} {} _Cv_any_notify_all(const _Cv_any_notify_all&) = delete; _Cv_any_notify_all& operator=(const _Cv_any_notify_all&) = delete; @@ -273,7 +273,8 @@ private: } }; -_EXPORT_STD inline void notify_all_at_thread_exit(condition_variable& _Cnd, unique_lock _Lck) { +_EXPORT_STD inline void notify_all_at_thread_exit(condition_variable& _Cnd, unique_lock _Lck) noexcept +/* strengthened */ { // register _Cnd for release at thread exit _Cnd._Register(_Lck, nullptr); } diff --git a/stl/inc/mutex b/stl/inc/mutex index 97a82f00e..0b2c36396 100644 --- a/stl/inc/mutex +++ b/stl/inc/mutex @@ -48,16 +48,8 @@ public: _Check_C_return(_Mtx_lock(_Mymtx())); } - _NODISCARD_TRY_CHANGE_STATE bool try_lock() { - const auto _Res = _Mtx_trylock(_Mymtx()); - switch (_Res) { - case _Thrd_success: - return true; - case _Thrd_busy: - return false; - default: - _Throw_C_error(_Res); - } + _NODISCARD_TRY_CHANGE_STATE bool try_lock() noexcept /* strengthened */ { + return _Mtx_trylock(_Mymtx()) == _Thrd_success; } void unlock() noexcept /* strengthened */ { @@ -66,7 +58,7 @@ public: using native_handle_type = void*; - _NODISCARD native_handle_type native_handle() { + _NODISCARD native_handle_type native_handle() noexcept /* strengthened */ { return _Mtx_getconcrtcs(_Mymtx()); } @@ -92,7 +84,8 @@ public: _EXPORT_STD class recursive_mutex : public _Mutex_base { // class for recursive mutual exclusion public: - recursive_mutex() : _Mutex_base(_Mtx_recursive) {} + recursive_mutex() noexcept // strengthened + : _Mutex_base(_Mtx_recursive) {} _NODISCARD_TRY_CHANGE_STATE bool try_lock() noexcept { return _Mutex_base::try_lock(); @@ -123,7 +116,7 @@ class unique_lock { // whizzy class with destructor that unlocks mutex public: using mutex_type = _Mutex; - unique_lock() noexcept : _Pmtx(nullptr), _Owns(false) {} + unique_lock() noexcept = default; _NODISCARD_CTOR_LOCK explicit unique_lock(_Mutex& _Mtx) : _Pmtx(_STD addressof(_Mtx)), _Owns(false) { // construct and lock @@ -131,7 +124,7 @@ public: _Owns = true; } - _NODISCARD_CTOR_LOCK unique_lock(_Mutex& _Mtx, adopt_lock_t) + _NODISCARD_CTOR_LOCK unique_lock(_Mutex& _Mtx, adopt_lock_t) noexcept // strengthened : _Pmtx(_STD addressof(_Mtx)), _Owns(true) {} // construct and assume already locked unique_lock(_Mutex& _Mtx, defer_lock_t) noexcept @@ -255,8 +248,8 @@ public: } private: - _Mutex* _Pmtx; - bool _Owns; + _Mutex* _Pmtx = nullptr; + bool _Owns = false; void _Validate() const { // check if the mutex can be locked if (!_Pmtx) { @@ -455,7 +448,8 @@ public: _MyMutex.lock(); } - lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) {} // construct but don't lock + lock_guard(_Mutex& _Mtx, adopt_lock_t) noexcept // strengthened + : _MyMutex(_Mtx) {} // construct but don't lock ~lock_guard() noexcept { _MyMutex.unlock(); @@ -476,7 +470,8 @@ public: _STD lock(_Mtxes...); } - explicit scoped_lock(adopt_lock_t, _Mutexes&... _Mtxes) : _MyMutexes(_Mtxes...) {} // construct but don't lock + explicit scoped_lock(adopt_lock_t, _Mutexes&... _Mtxes) noexcept // strengthened + : _MyMutexes(_Mtxes...) {} // construct but don't lock ~scoped_lock() noexcept { _STD apply([](_Mutexes&... _Mtxes) { (..., (void) _Mtxes.unlock()); }, _MyMutexes); @@ -498,7 +493,8 @@ public: _MyMutex.lock(); } - explicit scoped_lock(adopt_lock_t, _Mutex& _Mtx) : _MyMutex(_Mtx) {} // construct but don't lock + explicit scoped_lock(adopt_lock_t, _Mutex& _Mtx) noexcept // strengthened + : _MyMutex(_Mtx) {} // construct but don't lock ~scoped_lock() noexcept { _MyMutex.unlock(); @@ -514,9 +510,8 @@ private: template <> class scoped_lock<> { public: - explicit scoped_lock() {} - explicit scoped_lock(adopt_lock_t) {} - ~scoped_lock() noexcept {} + explicit scoped_lock() = default; + explicit scoped_lock(adopt_lock_t) noexcept /* strengthened */ {} scoped_lock(const scoped_lock&) = delete; scoped_lock& operator=(const scoped_lock&) = delete; @@ -600,7 +595,7 @@ _EXPORT_STD class condition_variable { // class for waiting for conditions public: using native_handle_type = _Cnd_t; - condition_variable() { + condition_variable() noexcept /* strengthened */ { _Cnd_init_in_situ(_Mycnd()); } @@ -619,7 +614,7 @@ public: _Cnd_broadcast(_Mycnd()); } - void wait(unique_lock& _Lck) { // wait for signal + void wait(unique_lock& _Lck) noexcept /* strengthened */ { // wait for signal // Nothing to do to comply with LWG-2135 because std::mutex lock/unlock are nothrow _Cnd_wait(_Mycnd(), _Lck.mutex()->_Mymtx()); } @@ -711,15 +706,15 @@ public: return _Wait_until1(_Lck, _Abs_time, _Pred); } - _NODISCARD native_handle_type native_handle() { + _NODISCARD native_handle_type native_handle() noexcept /* strengthened */ { return _Mycnd(); } - void _Register(unique_lock& _Lck, int* _Ready) { // register this object for release at thread exit + void _Register(unique_lock& _Lck, int* _Ready) noexcept { // register this object for release at thread exit _Cnd_register_at_thread_exit(_Mycnd(), _Lck.release()->_Mymtx(), _Ready); } - void _Unregister(mutex& _Mtx) { // unregister this object for release at thread exit + void _Unregister(mutex& _Mtx) noexcept { // unregister this object for release at thread exit _Cnd_unregister_at_thread_exit(_Mtx._Mymtx()); } @@ -765,14 +760,14 @@ private: struct _UInt_is_zero { const unsigned int& _UInt; - _NODISCARD bool operator()() const { + _NODISCARD bool operator()() const noexcept { return _UInt == 0; } }; _EXPORT_STD class timed_mutex { // class for timed mutual exclusion public: - timed_mutex() noexcept : _My_locked(0) {} + timed_mutex() = default; timed_mutex(const timed_mutex&) = delete; timed_mutex& operator=(const timed_mutex&) = delete; @@ -786,7 +781,7 @@ public: _My_locked = UINT_MAX; } - _NODISCARD_TRY_CHANGE_STATE bool try_lock() noexcept { // try to lock the mutex + _NODISCARD_TRY_CHANGE_STATE bool try_lock() noexcept /* strengthened */ { // try to lock the mutex lock_guard _Lock(_My_mutex); if (_My_locked != 0) { return false; @@ -838,12 +833,12 @@ public: private: mutex _My_mutex; condition_variable _My_cond; - unsigned int _My_locked; + unsigned int _My_locked = 0; }; _EXPORT_STD class recursive_timed_mutex { // class for recursive timed mutual exclusion public: - recursive_timed_mutex() noexcept : _My_locked(0) {} + recursive_timed_mutex() = default; recursive_timed_mutex(const recursive_timed_mutex&) = delete; recursive_timed_mutex& operator=(const recursive_timed_mutex&) = delete; @@ -953,7 +948,7 @@ public: private: mutex _My_mutex; condition_variable _My_cond; - unsigned int _My_locked; + unsigned int _My_locked = 0; thread::id _My_owner; }; _STD_END diff --git a/stl/inc/shared_mutex b/stl/inc/shared_mutex index 0e40e41dc..a194f17b6 100644 --- a/stl/inc/shared_mutex +++ b/stl/inc/shared_mutex @@ -72,7 +72,7 @@ private: static constexpr _Read_cnt_t _Max_readers = static_cast<_Read_cnt_t>(-1); public: - shared_timed_mutex() noexcept /* strengthened */ {} + shared_timed_mutex() = default; shared_timed_mutex(const shared_timed_mutex&) = delete; shared_timed_mutex& operator=(const shared_timed_mutex&) = delete; @@ -238,7 +238,7 @@ public: _NODISCARD_CTOR_LOCK shared_lock(mutex_type& _Mtx, try_to_lock_t) : _Pmtx(_STD addressof(_Mtx)), _Owns(_Mtx.try_lock_shared()) {} // construct with mutex and try to lock shared - _NODISCARD_CTOR_LOCK shared_lock(mutex_type& _Mtx, adopt_lock_t) + _NODISCARD_CTOR_LOCK shared_lock(mutex_type& _Mtx, adopt_lock_t) noexcept // strengthened : _Pmtx(_STD addressof(_Mtx)), _Owns(true) {} // construct with mutex and adopt ownership template diff --git a/stl/inc/thread b/stl/inc/thread index 0e1c13332..70cd8fb6e 100644 --- a/stl/inc/thread +++ b/stl/inc/thread @@ -207,12 +207,12 @@ namespace this_thread { class thread::id { // thread id public: - id() noexcept : _Id(0) {} // id for no thread + id() noexcept = default; // id for no thread private: - id(_Thrd_id_t _Other_id) : _Id(_Other_id) {} + explicit id(_Thrd_id_t _Other_id) noexcept : _Id(_Other_id) {} - _Thrd_id_t _Id; + _Thrd_id_t _Id = 0; friend thread::id thread::get_id() const noexcept; friend thread::id this_thread::get_id() noexcept; @@ -228,11 +228,11 @@ private: }; _NODISCARD inline thread::id thread::get_id() const noexcept { - return _Thr._Id; + return thread::id{_Thr._Id}; } _EXPORT_STD _NODISCARD inline thread::id this_thread::get_id() noexcept { - return _Thrd_id(); + return thread::id{_Thrd_id()}; } _EXPORT_STD inline void swap(thread& _Left, thread& _Right) noexcept { diff --git a/tests/libcxx/expected_results.txt b/tests/libcxx/expected_results.txt index dcb235dad..89da73120 100644 --- a/tests/libcxx/expected_results.txt +++ b/tests/libcxx/expected_results.txt @@ -664,6 +664,7 @@ std/time/time.syn/formatter.year_month_day_last.pass.cpp:0 FAIL std/time/time.syn/formatter.year_month_weekday.pass.cpp:0 FAIL # unused-variable warning +std/thread/thread.mutex/thread.lock/thread.lock.scoped/mutex.pass.cpp FAIL std/thread/thread.mutex/thread.mutex.requirements/thread.shared_mutex.requirements/thread.shared_mutex.class/default.pass.cpp:1 FAIL diff --git a/tests/libcxx/skipped_tests.txt b/tests/libcxx/skipped_tests.txt index 6f494826f..63af471c2 100644 --- a/tests/libcxx/skipped_tests.txt +++ b/tests/libcxx/skipped_tests.txt @@ -664,6 +664,7 @@ time\time.syn\formatter.year_month_day_last.pass.cpp time\time.syn\formatter.year_month_weekday.pass.cpp # unused-variable warning +thread\thread.mutex\thread.lock\thread.lock.scoped\mutex.pass.cpp thread\thread.mutex\thread.mutex.requirements\thread.shared_mutex.requirements\thread.shared_mutex.class\default.pass.cpp diff --git a/tests/std/tests/Dev11_1150223_shared_mutex/test.cpp b/tests/std/tests/Dev11_1150223_shared_mutex/test.cpp index 0f4a2925f..44ca1f2b6 100644 --- a/tests/std/tests/Dev11_1150223_shared_mutex/test.cpp +++ b/tests/std/tests/Dev11_1150223_shared_mutex/test.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__) @@ -35,10 +36,39 @@ STATIC_ASSERT(is_nothrow_destructible_v>); STATIC_ASSERT(is_nothrow_destructible_v); STATIC_ASSERT(is_nothrow_default_constructible_v); // N4928 [thread.mutex.class] +STATIC_ASSERT(is_nothrow_default_constructible_v); // strengthened +STATIC_ASSERT(is_nothrow_default_constructible_v); // strengthened +STATIC_ASSERT(is_nothrow_default_constructible_v); // strengthened STATIC_ASSERT(is_nothrow_default_constructible_v); // strengthened STATIC_ASSERT(is_nothrow_default_constructible_v); // strengthened STATIC_ASSERT(is_nothrow_default_constructible_v>); // N4928 [thread.lock.shared.cons]/1 STATIC_ASSERT(is_nothrow_default_constructible_v>); // N4928 [thread.lock.shared.cons]/1 +STATIC_ASSERT(is_nothrow_default_constructible_v); // strengthened + +STATIC_ASSERT(is_nothrow_constructible_v, shared_mutex&, adopt_lock_t>); // strengthened +STATIC_ASSERT( + is_nothrow_constructible_v, shared_mutex&, const adopt_lock_t&>); // strengthened +STATIC_ASSERT( + is_nothrow_constructible_v, shared_timed_mutex&, adopt_lock_t>); // strengthened +STATIC_ASSERT(is_nothrow_constructible_v, shared_timed_mutex&, + const adopt_lock_t&>); // strengthened + +// Also test strengthened exception specification for native_handle(). +STATIC_ASSERT(noexcept(declval().native_handle())); +#if _HAS_CXX20 +STATIC_ASSERT(noexcept(declval().native_handle())); +#endif // _HAS_CXX20 +STATIC_ASSERT(noexcept(declval().native_handle())); +STATIC_ASSERT(noexcept(declval().native_handle())); +STATIC_ASSERT(noexcept(declval().native_handle())); +STATIC_ASSERT(noexcept(declval().native_handle())); + +// Also test mandatory and strengthened exception specification for try_lock(). +STATIC_ASSERT(noexcept(declval().try_lock())); // strengthened +STATIC_ASSERT(noexcept(declval().try_lock())); // N4928 [thread.mutex.recursive] +STATIC_ASSERT(noexcept(declval().try_lock())); // strengthened +STATIC_ASSERT(noexcept(declval().try_lock())); // N4928 [thread.timedmutex.recursive] +STATIC_ASSERT(noexcept(declval().try_lock())); // strengthened void join_and_clear(vector& threads) { for (auto& t : threads) { diff --git a/tests/std/tests/P0156R2_scoped_lock/test.cpp b/tests/std/tests/P0156R2_scoped_lock/test.cpp index 8dd8021b8..2bedbcaf7 100644 --- a/tests/std/tests/P0156R2_scoped_lock/test.cpp +++ b/tests/std/tests/P0156R2_scoped_lock/test.cpp @@ -9,8 +9,40 @@ #include #include +#define STATIC_ASSERT(...) static_assert(__VA_ARGS__, #__VA_ARGS__) + using namespace std; +// nothrow-destructibility required by N4928 [res.on.exception.handling]/3 +STATIC_ASSERT(is_nothrow_destructible_v>); +STATIC_ASSERT(is_nothrow_destructible_v>); +#if _HAS_CXX17 +STATIC_ASSERT(is_nothrow_destructible_v>); +STATIC_ASSERT(is_nothrow_destructible_v>); +STATIC_ASSERT(is_nothrow_destructible_v>); +#endif // _HAS_CXX17 + +// Test mandatory and strengthened exception specification for default construction +STATIC_ASSERT(is_nothrow_default_constructible_v>); +#if _HAS_CXX17 +STATIC_ASSERT(is_nothrow_default_constructible_v>); // strengthened +#endif // _HAS_CXX17 + +// Test strengthened exception specification for adopt_lock construction +STATIC_ASSERT(is_nothrow_constructible_v, mutex&, adopt_lock_t>); +STATIC_ASSERT(is_nothrow_constructible_v, mutex&, const adopt_lock_t&>); +STATIC_ASSERT(is_nothrow_constructible_v, mutex&, adopt_lock_t>); +STATIC_ASSERT(is_nothrow_constructible_v, mutex&, const adopt_lock_t&>); +#if _HAS_CXX17 +STATIC_ASSERT(is_nothrow_constructible_v, adopt_lock_t>); +STATIC_ASSERT(is_nothrow_constructible_v, const adopt_lock_t&>); +STATIC_ASSERT(is_nothrow_constructible_v, adopt_lock_t, mutex&>); +STATIC_ASSERT(is_nothrow_constructible_v, const adopt_lock_t&, mutex&>); +STATIC_ASSERT(is_nothrow_constructible_v, adopt_lock_t, mutex&, recursive_mutex&>); +STATIC_ASSERT( + is_nothrow_constructible_v, const adopt_lock_t&, mutex&, recursive_mutex&>); +#endif // _HAS_CXX17 + // LOCK ORDERING: g_coutMutex is locked after all other locks and synchronizes // access to cout. using TestMutex = mutex; @@ -161,7 +193,7 @@ void exec_test_scoped_lock_adopts_one_mutex() { #if _HAS_CXX17 // Special case for 0 mutex types. void exec_test_scoped_lock_compiles_with_no_mutexes() { - scoped_lock<> takeNoLocks; + [[maybe_unused]] scoped_lock<> takeNoLocks; VERIFY_UNOWNED(g_mutexA); VERIFY_UNOWNED(g_mutexB); VERIFY_UNOWNED(g_mutexC); diff --git a/tests/std/tests/VSO_0000000_condition_variable_any_exceptions/test.cpp b/tests/std/tests/VSO_0000000_condition_variable_any_exceptions/test.cpp index 8555e3067..1d27c5b9a 100644 --- a/tests/std/tests/VSO_0000000_condition_variable_any_exceptions/test.cpp +++ b/tests/std/tests/VSO_0000000_condition_variable_any_exceptions/test.cpp @@ -172,6 +172,14 @@ void normal_tests() { test_case_condition_variable_any_less_accurate_time_points(); } +// Test strengthened exception specification of condition_variable::wait +static_assert(noexcept(declval().wait(declval&>())), + "condition_variable::wait that takes no predicate should be noexcept"); + +// Test strengthened exception specification of notify_all_at_thread_exit +static_assert(noexcept(notify_all_at_thread_exit(declval(), declval>())), + "notify_all_at_thread_exit should be noexcept"); + int main(int argc, char* argv[]) { std_testing::death_test_executive exec(normal_tests); exec.add_death_tests({test_case_condition_variable_any_lock_ex_should_terminate});