Использование 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 ответа:
При оценке до
true
is_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 ); } ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~