在派生类中使用noexcept

时间:2016-11-05 18:55:39

标签: c++ c++11 std traits 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

___编辑____________________________________________________________

在搜索了一段时间之后,关于 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

2 个答案:

答案 0 :(得分:1)

评估true is_move_constructible时的工作原理与is_constructible完全相同,后者反过来说

  

T是对象或引用类型以及变量定义T.   OBJ(STD :: declval()...);结构良好

我的猜测是,在您的情况下,定义BaseNok obj(...)并非真正格式良好,因为您既没有公开默认ctor(隐式删除)也没有任何其他可访问的ctor(受保护不是),因此它逃避false。 ( well-formeness 本身的定义虽然有争议)

ChildNok仍然是move_construbleble因为你的行动是公开的,其他情况是eval到false,因为std::is_move_constructible < BaseNok >::value已经false

编辑: 至于编辑过的问题,is_constructible提及的注意部分

  

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

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

我个人不确定它是否属于类型特征的监督或设计,LWG issue 2116

中涵盖了一些问题

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

答案 1 :(得分:0)

经过互联网的大量研究后,我发现唯一“可接受的”解决方案是实现我自己的特征,只处理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文件。这样,修改特征中的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 ); }
                        ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

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

相关问题