派生 类 中 noexcept 的使用

Usage of noexcept in derived classes

我在派生的 classes 上使用 noexcept 说明符时遇到问题,更准确地说,当父级是抽象 class(具有 protected 构造函数时)。

以下是我声明我的 classes 的方式的示例。

我错过了什么吗? std::is_nothrow_move_constructible 是在派生 class 声明中使用的正确特征还是我应该使用其他东西?

#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

___ 编辑 ____________________________________________________________

搜索了一段时间后,关于 Oleg Bogdanov 的答案,不幸的是,似乎无法将 protected 构造函数与 [=21] 的用法结合起来=].

我正在编写摘要 classes 并声明构造函数 protected 仅用于文档目的。现在,构造函数回到 public 但我面临另一个问题:

作为一个抽象 class 不能实例化,std::is_nothrow_move_constructible<BaseClass> returns false 和所有派生的 classes 永远不能被标记为不抛出异常,即使如果他们不是。

参见下面的示例:

#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

当评估为 true is_move_constructible works exactly like is_constructible 时,这反过来表示

T is an object or reference type and the variable definition T obj(std::declval()...); is well-formed

我的猜测是,在你的情况下,定义 BaseNok obj(...) 并不是真正的 格式正确的 ,因为你既没有 public 默认构造函数(它隐含removed) 或任何其他可访问的 ctor(protected 不是),因此它评估为 false。 (健康的定义本身是有争议的)

ChildNok 仍然是 move_constructible,因为你将其移动到 ctor public,其他情况评估为 false,正是因为 std::is_move_constructible < BaseNok >::value 已经是 false


编辑: 至于已编辑的问题,is_constructiblenote 部分提到

In many implementations, is_nothrow_constructible also checks if the destructor throws because it is effectively noexcept(T(arg))

当您将析构函数保持为纯虚拟时,它可能无法通过检查。

我个人不确定这是类型特征的疏忽还是设计,LWG issue 2116

中涵盖了一些问题

我不是在这里提出一个可扩展的解决方案,但是你为什么不暂时无条件地标记你的派生 类 noexcept,因为 base 也是 noexcept()

在互联网上进行大量研究后,我发现唯一的 "acceptable" 解决方案是实现我自己的特征,只处理 noexcept-ness。是否构造对象的能力被忽略,因为这是 abstract 类.

问题的原因

这是我在我的库中实现的。代码示例旁边给出了解释。
(注意:az:: 命名空间和 AZ_ 前缀标识我的库提供的 material。)

#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 值(通过宏)更新声明和定义(易于维护)。

如您所见,Foo 默认构造函数的 noexcept 说明符不再被其不可构造的方面所隐藏。

这段代码在 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 ); }
                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

希望这可以帮助面临同样问题的人。 :)