【问题标题】:std::throw_with_nested expects default constructor for virtual base class of Exception?std::throw_with_nested 期望异常虚拟基类的默认构造函数?
【发布时间】:2014-08-15 10:01:17
【问题描述】:

为什么这不能编译(尝试使用 Clang 3.4.2 和 GCC 版本 4.7.4、4.8.3 和 4.9.1):

#include <exception>

struct Base {
  inline Base(int) {}
  virtual void f() {}
};

struct Derived: virtual Base {
  inline Derived() : Base(42) {}
};

int main() {
  std::throw_with_nested(Derived());
  return 0;
}

来自 GCC 4.9.1 的错误:

In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/exception:163:0,
                from test.cpp:1:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/nested_exception.h: In instantiation of 'std::_Nested_exception<_Except>::_Nested_exception(_Except&&) [with _Except = Derived]':
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/nested_exception.h:126:60:   required from 'void std::__throw_with_nested(_Ex&&, ...) [with _Ex = Derived]'
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/nested_exception.h:140:58:   required from 'void std::throw_with_nested(_Ex) [with _Ex = Derived]'
test.cpp:13:35:   required from here
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/nested_exception.h:81:45: error: no matching function for call to 'Base::Base()'
      : _Except(static_cast<_Except&&>(__ex))
                                            ^
/usr/lib/gcc/x86_64-pc-linux-gnu/4.9.1/include/g++-v4/bits/nested_exception.h:81:45: note: candidates are:
test.cpp:4:10: note: Base::Base(int)
  inline Base(int) {}
          ^
test.cpp:4:10: note:   candidate expects 1 argument, 0 provided
test.cpp:3:8: note: constexpr Base::Base(const Base&)
struct Base {
        ^
test.cpp:3:8: note:   candidate expects 1 argument, 0 provided
test.cpp:3:8: note: constexpr Base::Base(Base&&)
test.cpp:3:8: note:   candidate expects 1 argument, 0 provided

如果我省略 virtual 关键字,我不会收到任何错误。这是 GCC 和 Clang 或 libstd++ 错误,还是我做错了什么?

PS:请注意,virtual void f() {}this bug 的解决方法。

【问题讨论】:

  • 如果你在类的声明中声明了一个成员,那么标记它们是多余的inline:“在类定义中定义的函数是内联函数。” §7.1.2 [dcl.fct.spec]
  • 为什么要做虚拟继承呢?
  • @RobertAllanHenniganLeahy 是的,错过了结构,编辑了我的评论

标签: c++ exception c++11 exception-handling libstdc++


【解决方案1】:

问题是当你有虚拟继承时,最后一个派生类负责构建基类。

在这种情况下,库正在尝试创建更多从您的类派生的类,然后无法构造它们。

当您删除“虚拟”继承时,最终类可以毫无问题地从您的继承中派生。

遇到错误消息的实现“代码”...

template<typename _Except>
struct _Nested_exception : public _Except, public nested_exception 
{
  explicit _Nested_exception(_Except&& __ex)
  : _Except(static_cast<_Except&&>(__ex))
 { }
};

所以它需要在其构造函数中构造一个Base,因为它现在是最派生的类,并且不能正确地 dp。

【讨论】:

    【解决方案2】:

    标准规定:

    对于每个指定为虚拟的不同基类,最派生对象应包含该类型的单个基类子对象。

    §10.1 [class.mi]

    另外,关于std::throw_with_nested

    [[noreturn]] template &lt;class T&gt; void throw_with_nested(T&amp;&amp; t);

    U 成为remove_reference&lt;T&gt;::type

    要求: U 应为 CopyConstructible

    抛出: 如果 U 是非联合类类型,它不是从 nested_exception 派生的,则从 Unested_exception 公开派生并构造的未指定类型的异常来自std::forward&lt;T&gt;(t),否则来自std::forward&lt;T&gt;(t)

    §18.8.6 [except.nested]

    如果您查看bits/nested_exception.h,您会发现以下内容,这是该库在 libstdc++ 中为std::throw_with_nested 创建的基类:

    template<typename _Except>
    struct _Nested_exception : public _Except, public nested_exception
    {
      explicit _Nested_exception(_Except&& __ex)
      : _Except(static_cast<_Except&&>(__ex))
      { }
    };
    

    这派生自您的 Derived 类,并尝试使用您的复制或移动构造函数初始化自身,这两者都是隐式定义的。

    在非虚拟继承的情况下,这很好,因为基类被隐式复制/移动构造函数递归复制或移动,如标准中所述:

    非联合类X 的隐式定义复制/移动构造函数执行其基类和成员的成员复制/移动。

    §12.8 [class.copy]

    但是,由于虚拟继承和§10.1 [class.mi](上面引用),它是基类的责任(在这种情况下_Nested_exception初始化Base不是 Derived 班级的责任。

    因此,尝试寻找一个默认构造函数,但失败了,因为它被隐式删除了,因为还有另一个用户提供的构造函数,标准规定:

    如果类 X 没有用户声明的构造函数,则没有参数的构造函数被隐式声明为默认构造函数。

    §12.1 [class.ctor]

    因此,此代码被编译器正确拒绝

    【讨论】:

    • 等等,你在我的回答中指出这个实现与[except.nested]的要求相矛盾,然后发布你自己的回答说因为它是这样实现的,所以编译器是正确的? T 是否符合标准中规定的throw_with_nested 的要求?如果是这样,该程序必须被接受。如果不是,则不需要。实现方式如何实现并不重要。
    • @hvd:不,在你的回答中,我评论说这与复制构造函数无关(因为 §12.8 [class.copy]),这是正确的。
    • 我的答案不是使用复制构造函数,但_Nested_exception 也不是:引用基类的构造函数不是复制构造函数。默认构造函数和随机其他构造函数的问题完全相同。但同样,这是一个与此处无关的实现细节。
    • 哦,这不是我想说的,这就是为什么我包含编译器的错误消息,显示Derived2 的构造函数试图构造Base。但我删除了它,因为你留下的评论似乎是正确的:UCopyConstructible,所以程序看起来符合标准的要求,实现应该接受它。 (我不明白这怎么可能,但我可能会遗漏一些东西。)
    • 标准确实从std::forward&lt;T&gt;(t)构造嵌套类,但只是隐含地需要一个自定义构造函数:默认情况下,这样的派生类不会有 any 构造函数采用T,其意图显然不会是几乎任何throw_with_nested 的使用都无效,因此需要实现添加他们需要的任何构造函数,并以使throw_with_nested 的方式实现它们工作。在处理虚拟基础时,这似乎需要编译器魔法......
    猜你喜欢
    • 1970-01-01
    • 2017-02-10
    • 2018-04-18
    • 2021-06-02
    • 1970-01-01
    • 2012-07-26
    • 1970-01-01
    • 2018-06-29
    相关资源
    最近更新 更多