什么决定std::runtime_error子类是否为is_nothrow_copy_constructible?
std::runtime_error subclass's is_nothrow_copy_constructible status vary across environments? Let's break down exactly what's going on here—your custom exception's noexcept copy constructibility boils down to subtle standard library implementation details and environment-specific compile configurations, even when compiler and library versions look identical.
Key Factors Determining is_nothrow_copy_constructible
1. libstdc++'s implementation of std::exception
At the root of this is how std::exception (the base class of std::runtime_error) is implemented:
- The C11+ standard allows, but does not require,
std::exception's copy constructor to benoexcept. This gives libstdc maintainers flexibility in how they implement it. - Starting with GCC 5.x, libstdc++ defaults to marking
std::exception's copy constructor asnoexcept. Since yourcustom_errorinherits fromstd::runtime_error(which inherits fromstd::exception), your class automatically gets thisnoexceptattribute for its implicit copy constructor (assuming you don't explicitly override it to be non-noexcept). - GCC 4.x's libstdc++ doesn't include this
noexceptmarker onstd::exception, so your subclass won't have it either.
2. Standard library compilation flags
Even with the same libstdc++ version (like your 6.0.25), compile-time configuration can change std::exception's behavior:
- Distributions sometimes compile libstdc++ with debug flags like
_GLIBCXX_DEBUG. In debug mode, libstdc++ often removesnoexceptmarkers fromstd::exception's copy constructor because debug checks might throw exceptions (which would violatenoexcept). - Your Travis-CI Ubuntu 14.04 environment is likely using a libstdc++ build with these debug flags enabled, whereas your local Ubuntu 18.04 setup uses the default non-debug build—hence the discrepancy even with matching version numbers.
3. Implicit copy constructor rules
When you don't define a copy constructor for custom_error, the compiler generates one implicitly. The noexcept status of this implicit constructor depends entirely on the noexcept status of all base class and member variable copy constructors:
- If
std::runtime_error's copy constructor isnoexcept(and all your custom members also havenoexceptcopy constructors), your implicit copy constructor will benoexcept. - If any base or member's copy constructor isn't
noexcept, your implicit constructor won't be either.
Breaking Down Your Specific Scenarios
- Compiler Explorer GCC 5.x+ vs 4.x: GCC 5.x+ uses the newer libstdc++ with
std::exception'snoexceptcopy constructor, so yourcustom_errorisis_nothrow_copy_constructible. GCC 4.x uses the older implementation without the marker, so it's not. - Local Ubuntu 18.04: Your system's libstdc++.so.6.0.25 is compiled in non-debug mode, so
std::exception's copy constructor isnoexcept. Both GCC and Clang link against this library, so your class inherits thenoexceptproperty regardless of compiler. - Travis-CI Ubuntu 14.04: Even with matching compiler and library versions, the environment's libstdc++ is built with debug flags (like
_GLIBCXX_DEBUG) that remove thenoexceptmarker fromstd::exception. This trickles down to yourcustom_error, making it notis_nothrow_copy_constructible.
Fixing the cert-err60-cpp Warning
If you want to guarantee your custom_error is noexcept copy constructible (and silence the clang-tidy warning), explicitly define your copy constructor with noexcept:
class custom_error : public std::runtime_error { public: using std::runtime_error::runtime_error; // Explicitly default and mark copy operations as noexcept custom_error(const custom_error&) noexcept = default; custom_error& operator=(const custom_error&) noexcept = default; };
Just keep in mind: this is only safe if std::runtime_error's copy constructor is actually noexcept in all environments you target. If you ever run on a libstdc++ build where it's not, marking your constructor noexcept will lead to undefined behavior if a copy operation throws.
内容的提问来源于stack exchange,提问作者fcooper8472

