问题
I encounter an issue while using the noexcept
specifier on derived classes, more precisely when the parent is an abstract class (has protected
constructors).
Hereafter is an example of the way I declare my classes.
- With a
public
constructor in the base class: Everything is ok. - Same code with
protected
and the derived class is no more "nothrow movable".
Do I miss something? Is std::is_nothrow_move_constructible
the correct traits to use in derived class declarations or should I use something else?
#include <cstdlib>
#include <iostream>
class BaseOk
{
public:
BaseOk ( BaseOk&& other ) noexcept {}
};
class BaseNok
{
protected:
BaseNok ( BaseNok&& other ) noexcept {}
};
class ChildOk : public BaseOk
{
public:
ChildOk ( ChildOk&& other ) noexcept ( std::is_nothrow_move_constructible < BaseOk >::value )
: BaseOk ( std::move ( other ) ) {}
};
class ChildNok : public BaseNok
{
public:
ChildNok ( ChildNok&& other ) noexcept ( std::is_nothrow_move_constructible < BaseNok >::value )
: BaseNok ( std::move ( other ) ) {}
};
int main ()
{
std::cout << std::boolalpha;
std::cout << "Is BaseOk move constructible? " << std::is_move_constructible < BaseOk >::value << '\n';
std::cout << "Is ChildOk move constructible? " << std::is_move_constructible < ChildOk >::value << '\n';
std::cout << '\n';
std::cout << "Is BaseOk nothrow move constructible? " << std::is_nothrow_move_constructible < BaseOk >::value << '\n';
std::cout << "Is ChildOk nothrow move constructible? " << std::is_nothrow_move_constructible < ChildOk >::value << '\n';
std::cout << '\n';
std::cout << "Is BaseNok move constructible? " << std::is_move_constructible < BaseNok >::value << '\n';
std::cout << "Is ChildNok move constructible? " << std::is_move_constructible < ChildNok >::value << '\n';
std::cout << '\n';
std::cout << "Is BaseNok nothrow move constructible? " << std::is_nothrow_move_constructible < BaseNok >::value << '\n';
std::cout << "Is ChildNok nothrow move constructible? " << std::is_nothrow_move_constructible < ChildNok >::value << '\n';
std::cout << std::endl;
return EXIT_SUCCESS;
}
Output:
Is BaseOk move constructible? true
Is ChildOk move constructible? true
Is BaseOk nothrow move constructible? true
Is ChildOk nothrow move constructible? true
Is BaseNok move constructible? false
Is ChildNok move constructible? true
Is BaseNok nothrow move constructible? false
Is ChildNok nothrow move constructible? false
___ EDIT ____________________________________________________________
After searching around for a while, and regarding to the andswer of Oleg Bogdanov, it unfortunately seems not possible to combine protected
constructors with usage of noexcept ( is_nothrow_... )
.
I was writting abstract classes and declared constructors protected
for documentation purposes only. Now, constructors are back to public
but I'm facing another problem:
As an abstract class cannot be instanciated, std::is_nothrow_move_constructible<BaseClass>
returns false
and all derived classes can never be tagged as not throwing exceptions even if they are not.
See example below:
#include <cstdlib>
#include <iostream>
class Foo
{
public:
Foo ( Foo&& other ) noexcept {}
virtual ~Foo () = 0; // Removing '= 0' makes both outputs print 'true'.
};
Foo::~Foo () {}
class Bar : public Foo
{
public:
Bar ( Bar&& other ) noexcept ( std::is_nothrow_move_constructible < Foo >::value )
: Foo ( std::move ( other ) ) {}
};
int main ()
{
std::cout << std::boolalpha;
std::cout << "Foo: " << std::is_nothrow_move_constructible < Foo >::value << '\n';
std::cout << "Bar: " << std::is_nothrow_move_constructible < Bar >::value << '\n';
return EXIT_SUCCESS;
}
Output:
Foo: false
Bar: false
回答1:
When evaluating to true
is_move_constructible works exactly like is_constructible, which in turn says that
T is an object or reference type and the variable definition T obj(std::declval()...); is well-formed
My guess is that in your case, definition BaseNok obj(...)
is not really well-formed, because you neither have public default ctor (its implicitly removed) nor any other accessible ctor (protected is not), thus it evals to false
. (The definition of well-formeness itself is arguable though)
ChildNok is still move_constructible because you made its move ctor public, other cases eval to false
exactly because std::is_move_constructible < BaseNok >::value
is already false
Edit: As for edited question, note section of is_constructible mentions
In many implementations, is_nothrow_constructible also checks if the destructor throws because it is effectively noexcept(T(arg))
When you keep your destructor pure virtual, it probably fails the check.
I am personally not sure if it's oversight or by-design of type traits, some issues are covered in LWG issue 2116
I'm not proposing a scalable solution here, but why would not you mark your derived classes unconditionally noexcept for now, given that base is noexcept() too
回答2:
After lot of researches around the Internet, I found that the only "acceptable" solution is to implement my own traits, dealing only with the noexcept
-ness. The ability to construct an object or not is ignored, as this is the reason of the issue with abstract classes.
Here is what I implemented in my library. Explanations are given next to code sample.
(Note: The az::
namespace and AZ_
prefixes identify the material provided by my library.)
#include <cstdlib>
#include <iostream>
// --- Default traits ---
namespace az
{
template < typename CLASS > struct has_noexcept_default_constructor { static const bool value = false; };
template < typename CLASS > struct has_noexcept_copy_constructor { static const bool value = false; };
template < typename CLASS > struct has_noexcept_move_constructor { static const bool value = false; };
template < typename CLASS > struct has_noexcept_copy_operator { static const bool value = false; };
template < typename CLASS > struct has_noexcept_move_operator { static const bool value = false; };
}
// --- Helper macros ---
#define AZ_SET_NOEXCEPT_DEFAULT_CONSTRUCTOR( CLASS, VALUE ) \
template <> struct az::has_noexcept_default_constructor < class CLASS > { static const bool value = ( VALUE ); }
#define AZ_SET_NOEXCEPT_COPY_CONSTRUCTOR( CLASS, VALUE ) \
template <> struct az::has_noexcept_copy_constructor < class CLASS > { static const bool value = ( VALUE ); }
#define AZ_SET_NOEXCEPT_MOVE_CONSTRUCTOR( CLASS, VALUE ) \
template <> struct az::has_noexcept_move_constructor < class CLASS > { static const bool value = ( VALUE ); }
#define AZ_SET_NOEXCEPT_COPY_OPERATOR( CLASS, VALUE ) \
template <> struct az::has_noexcept_copy_operator < class CLASS > { static const bool value = ( VALUE ); }
#define AZ_SET_NOEXCEPT_MOVE_OPERATOR( CLASS, VALUE ) \
template <> struct az::has_noexcept_move_operator < class CLASS > { static const bool value = ( VALUE ); }
// --- Foo class ---
AZ_SET_NOEXCEPT_DEFAULT_CONSTRUCTOR ( Foo, true );
AZ_SET_NOEXCEPT_MOVE_CONSTRUCTOR ( Foo, true );
class Foo
{
public:
Foo () noexcept ( az::has_noexcept_default_constructor < Foo >::value ) {}
Foo ( Foo&& other ) noexcept ( az::has_noexcept_move_constructor < Foo >::value ) {}
virtual ~Foo () = 0;
};
Foo::~Foo () {}
// --- Bar class ---
AZ_SET_NOEXCEPT_DEFAULT_CONSTRUCTOR ( Bar, az::has_noexcept_default_constructor < Foo >::value );
class Bar : public Foo
{
public:
Bar () noexcept ( az::has_noexcept_default_constructor < Bar >::value ) {}
Bar ( Bar&& other ) noexcept ( az::has_noexcept_move_constructor < Bar >::value ) : Foo ( std::move ( other ) ) {}
};
// --- Tests ---
int main ()
{
std::cout << std::boolalpha;
bool fooHasNedc = az::has_noexcept_default_constructor < Foo >::value;
bool fooHasNecc = az::has_noexcept_copy_constructor < Foo >::value;
bool fooHasNemc = az::has_noexcept_move_constructor < Foo >::value;
bool fooIsNtdc = std::is_nothrow_default_constructible < Foo >::value;
bool fooIsNtcc = std::is_nothrow_copy_constructible < Foo >::value;
bool fooIsNtmc = std::is_nothrow_move_constructible < Foo >::value;
std::cout << "Foo has noexcept def/copy/move constructors: " << fooHasNedc << " " << fooHasNecc << " " << fooHasNemc << '\n';
std::cout << "Foo is nothrow def/copy/move constructible: " << fooIsNtdc << " " << fooIsNtcc << " " << fooIsNtmc << '\n';
std::cout << std::endl;
bool barHasNedc = az::has_noexcept_default_constructor < Bar >::value;
bool barHasNecc = az::has_noexcept_copy_constructor < Bar >::value;
bool barHasNemc = az::has_noexcept_move_constructor < Bar >::value;
bool barIsNtdc = std::is_nothrow_default_constructible < Bar >::value;
bool barIsNtcc = std::is_nothrow_copy_constructible < Bar >::value;
bool barIsNtmc = std::is_nothrow_move_constructible < Bar >::value;
std::cout << "Bar has noexcept def/copy/move constructors: " << barHasNedc << " " << barHasNecc << " " << barHasNemc << '\n';
std::cout << "Bar is nothrow def/copy/move constructible: " << barIsNtdc << " " << barIsNtcc << " " << barIsNtmc << '\n';
std::cout << std::endl;
return EXIT_SUCCESS;
}
Output:
Foo has noexcept def/copy/move constructors: true false true
Foo is nothrow def/copy/move constructible: false false false
Bar has noexcept def/copy/move constructors: true false false
Bar is nothrow def/copy/move constructible: true false false
The default traits provide a default implementation for throwing constructors & assignment operators.
Helper macros make specialized traits implementation very simple. They are used only once in the header file. Then, the trait is used in both .hpp
and .cpp
files. This way, modifying a noexcept
value in the trait (through the macro) updates both declaration and definition (ease maintenability).
As you can see, the noexcept
specifier of the Foo default constructor is no more hidden by its non-constructible aspect.
This code perfecty compile under VisualStudio 2015 and clang++.
g++ generates the following error (I'm sure it can be fixed in a way or another ^^):
main.cpp:19:24: error: specialization of 'template<class CLASS> struct az::has_noexcept_default_constructor' in different namespace [-fpermissive]
template <> struct az::has_noexcept_default_constructor < class CLASS > { static const bool value = ( VALUE ); }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Hope this could help people facing the same issue. :)
来源:https://stackoverflow.com/questions/40441994/usage-of-noexcept-in-derived-classes