在派生类中使用noexcept
Usage of noexcept in derived classes
我在派生类上使用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 ); }
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
希望这可以帮助人们面对同样的问题。:)
- 为什么使用 "this" 指针调用派生成员函数?
- 具有奇怪重复模板模式的派生类中的成员变量已损坏
- 在派生函数中指定void*参数
- 如何通过派生类函数更改基类中的向量
- 如何委托派生类使用其父构造函数?
- 如何使用单独文件中的派生类访问友元函数对象
- 派生类销毁的最佳实践是什么
- 如何使用基类指针引用派生类成员
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 如何引用基类的派生类?
- 存储模板类型以强制转换回派生<T>
- 需要从 istream 和 ostream 派生 iostream
- 在 C++ 中用派生类型重写成员函数
- 具有多个类、派生类的C++正向声明
- 有没有一种"cleaner"的方法可以在指向基的指针向量中找到派生类的第一个实例?
- 用常见虚拟函数实现的任意组合来实现派生类的正确方法是什么
- 如果基类包含双指针成员,则派生类的构造函数
- 派生类构造函数上的Noexcept promise:可以在没有对基构造函数上的Noexcept进行承诺的情况下使用
- 在派生类中使用noexcept