在派生类中使用noexcept

Usage of noexcept in derived classes

本文关键字:noexcept 派生      更新时间:2023-10-16

我在派生类上使用noexcept说明符时遇到了一个问题,更准确地说,当父类是抽象类(具有protected构造器)时。

Hereafter是我声明类的一个例子。

  • 在基类中使用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

___编辑 ____________________________________________________________

在搜索了一段时间后,关于Oleg Bogdanov的答案,不幸的是,似乎不可能将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

当求值到true时,is_move_constructible的工作方式与is_constructible完全相同,这反过来又表示

T是对象或引用类型和变量定义Tobj (std:: declval()…);是格式良好的

我的猜测是,在您的情况下,定义BaseNok obj(...)不是真正的格式良好的,因为您既没有公共默认的ctor(它隐式地删除)也没有任何其他可访问的ctor(受保护的不是),因此它的值为false。(完备性的定义本身是有争议的)

ChildNok仍然是move_constructible,因为你把它的move函数设为public,在其他情况下,计算为false,因为std::is_move_constructible < BaseNok >::value已经是false


编辑:对于编辑的问题,注释 is_constructible部分提到

在许多实现中,is_nothrow_constructible也检查析构函数是否抛出,因为它实际上是noexcept(T(arg))

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

我个人不确定这是类型特征的疏忽还是故意设计的,一些问题在LWG第2116期中讨论了

我不是在这里提出一个可扩展的解决方案,但为什么你不无条件地标记你的派生类noexcept现在,鉴于基础是noexcept()太

在网上进行了大量的研究后,我发现唯一"可以接受"的解决方案是实现我自己的特征,只处理noexcept -ness。构造对象或不构造对象的能力被忽略,因为这就是抽象类问题的原因。

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

#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

默认特征提供了抛出构造函数的默认实现&赋值操作符。

Helper宏使得专门化特性的实现非常简单。它们只在头文件中使用一次。然后,在.hpp.cpp文件中都使用该特性。这样,修改trait中的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 ); }
                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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