Использование 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 2

2 ответа:

При оценке до true is_move_constructible работает точно так же, как is_constructible, что в свою очередь говорит, что

T-тип объекта или ссылки и определение переменной T obj (std:: declval ()...); хорошо сформирован

Я предполагаю, что в вашем случае определение BaseNok obj(...) на самом деле не является хорошо сформированным , потому что у вас нет ни общедоступного ctor по умолчанию (его неявно удаляют), ни любого другого доступного ctor (защищенного нет), таким образом, это evals to false. (Определение хорошей формы само по себе спорно)

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 ); }
                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Надеюсь, это поможет людям, столкнувшимся с той же проблемой. :)