std::throw_with_nested期望Exception的虚基类的默认构造函数

std::throw_with_nested expects default constructor for virtual base class of Exception?

本文关键字:基类 默认 构造函数 期望 throw with nested std Exception      更新时间:2023-10-16

为什么不能编译(在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() {}是这个错误的解决方案。

问题是,当您使用虚拟继承时,最后一个派生类负责构造基类。

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

当你删除"虚拟"继承时,最终的类可以毫无问题地从你的类派生出来。

打出错误信息的实现"代码"…

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

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

标准声明:

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

§10.1 [class.mi]

对于std::throw_with_nested:

[[noreturn]] template <class T> void throw_with_nested(T&& t);

Uremove_reference<T>::type

要求: U应为CopyConstructible

抛出:如果U不是从nested_exception派生的非联合类类型,则抛出一个未指定类型的异常,该异常从Unested_exception公开派生,并从std::forward<T>(t)构造,否则为std::forward<T>(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类派生出来的,并试图用你的copy或move构造函数初始化自己,这两个构造函数都是隐式定义的。

在非虚继承的情况下,这是可以的,因为基类是通过隐式复制/移动构造函数递归地复制或移动的,如标准中所述:

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

§12.8 [class.copy]

然而,由于虚继承,和§10.1[类。(上面引用的),它是基类的责任(在本例中是_Nested_exception初始化Base而不是你的Derived类的责任。

因此,尝试查找默认构造函数,但失败了,因为它被隐式删除了,因为存在另一个用户提供的构造函数,并且标准状态为:

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

§12.1 [class.ctor]

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