Использование noexcept в производных классах
Я сталкиваюсь с проблемой при использовании спецификатора noexcept для производных классов, точнее, когда родительский класс является абстрактным классом (имеет конструкторы protected).
- с конструктором
publicв базовом классе: все в порядке. - Тот же код с
protectedи производный класс больше не является "nothrow movable".
Я что-то пропустил? Является ли std::is_nothrow_move_constructible корректными признаками для использования в производном классе объявления или я должен использовать что-то другое?
#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;
}
Вывод:
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
___ править ____________________________________________________________
После некоторого поиска вокруг и относительно andswer изОлега Богданова , К сожалению, кажется невозможным объединить protected конструкторы с использованием noexcept ( is_nothrow_... ).
Я писал абстрактные классы и объявлял конструкторы protected только для документации. Итак, конструкторы вернулись к public, но я столкнулся с другой проблемой:
Поскольку абстрактный класс не может быть создан, std::is_nothrow_move_constructible<BaseClass> возвращает false, и все производные классы никогда не могут быть помечены как не создающие исключений, даже если они ими не являются.
Смотрите пример ниже:
#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;
}
Вывод:
Foo: false
Bar: false
2 ответа:
При оценке до
trueis_move_constructible работает точно так же, как is_constructible, что в свою очередь говорит, чтоT-тип объекта или ссылки и определение переменной T obj (std:: declval ()...); хорошо сформирован
Я предполагаю, что в вашем случае определение
BaseNok obj(...)на самом деле не является хорошо сформированным , потому что у вас нет ни общедоступного ctor по умолчанию (его неявно удаляют), ни любого другого доступного ctor (защищенного нет), таким образом, это evals tofalse. (Определение хорошей формы само по себе спорно)ChildNok все еще move_constructible, потому что вы сделали его перемещение ctor публичным, другие случаи eval к
falseименно потому, чтоstd::is_move_constructible < BaseNok >::valueужеfalse
Править: Что касается отредактированного вопроса, Примечание раздел is_constructible упоминает
Во многих реализациях is_nothrow_constructible также проверяет, бросает ли деструктор, потому что он эффективно noexcept(T (arg))
Когда вы сохраняете свой деструктор чисто виртуальным,он, вероятно, не проходит проверку.
Лично я не уверен, является ли это оплошностью или побочным дизайном черт типа, некоторые вопросы рассматриваются в lwg issue 2116
Я не предлагаю здесь масштабируемое решение, но почему бы вам не отметить ваши производные классы безоговорочно noexcept на данный момент, учитывая, что base-это noexcept () тоже
После множества исследований в Интернете я обнаружил, что единственное "приемлемое" решение-это реализовать свои собственные черты, имея дело только с
Вот что я реализовал в своей библиотеке. Пояснения даются рядом с образцом кода.noexcept-ностью. Возможность построить объект или нет игнорируется, так как это является причиной проблемы с абстрактными классами.
(Примечание: пространство именaz::и префиксыAZ_идентифицируют материал, предоставленный my библиотека.)#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; }Вывод:
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Признаки по умолчанию обеспечивают реализацию по умолчанию для метания конструкторов и операторов присваивания.
Вспомогательных макросов сделать специализированные черты характера реализации очень простой. Они используются только один раз в заголовочном файле. Затем признак используется как в файлах
.hpp, так и в файлах.cpp. Таким образом, изменение значенияnoexceptв черте (через макрос) обновляет как объявление, так и определение (простота обслуживания).Как видите, спецификатор
noexceptконструктора Foo default больше не скрыт его неконструктивным аспектом.Этот код perfecty компилируют под VisualStudio 2015 и clang++.
g++ генерирует следующую ошибку (я уверен, что ее можно исправить тем или иным способом^^):Надеюсь, это поможет людям, столкнувшимся с той же проблемой. :)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 ); } ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~