抛出从不可复制派生的可复制 class

Throwing copyable class deriving from noncopyable

我有一个将异常定义为不可复制的框架 class,我们从中派生了一个可复制的 class(定义了一个调用非复制基数的复制构造函数 class构造函数)

这在 g++ 下有效,但在 MSVC 2013 下无效。

以下代码将重现该问题:

#include <iostream>

using namespace std;

#if defined _MSC_VER
#define __PRETTY_FUNCTION__ __FUNCTION__
#endif

class u {
  u(const u&) = delete;
  const u& operator=(const u&) = delete;/* the library we use defines it as const u& */
public:
  u() { cout << __PRETTY_FUNCTION__ << "def" << endl; }
protected:
  explicit u(int i) { cout << __PRETTY_FUNCTION__ << "int: " << i << endl; }
};

class e : public u {
public:
  e() { cout << __PRETTY_FUNCTION__ << "def" << endl; }
  e(const e& _e) : u(1) { cout << __PRETTY_FUNCTION__ << "cpy" << endl; }
  e& operator=(const e& _e) { cout << __PRETTY_FUNCTION__ << endl; return *this; }
};

int foo() {
  e _e;
  throw _e;

  return 0;
}

int main() {
  try {
    foo();
  } catch(const e& _e) {
    cout << "in catch e" << endl;
  } catch(...) {
    cout << "in catch..." << endl;
  }
#if defined _MSC_VER
  cout << "press enter to exit" << endl;
  cin.get();
#endif
  return 0;
}

MSVC 在函数 foo() 的末尾抱怨 Error 1 error C2280: 'u::u(const u &)' : attempting to reference a deleted function

g++ 和 clang 都编译代码,它们根本不使用复制构造函数(e 对象被移动),但是如果 e 不是可复制构造的,它们也不会编译。

编辑:我已编辑代码以强制复制。

顺便说一句,如果未删除 u 复制函数(也未定义,pre-c++11 不可复制),MSVC 在 u::u(const u&) 查找期间的 link 阶段失败. (未解决的外部问题)

是我的代码有缺陷,还是 MSVC 有这个错误?

MSVC 2013 确实需要可复制异常 类。此外,它的构造函数必须是显式的。以下代码对于 MSVC 2013 是不正确的:

class Ex {
public:
   Ex(){}
private:
   explicit Ex(const Ex&);
};

int main()
{
    throw Ex(); // error
}

看来你无法正确处理这个问题;任何带有指针的魔法都会导致不正确的对象删除。

我只看到一种解决方案:将您的问题通知框架开发人员。

首先,eu 都有用户声明的复制构造函数。这意味着它们没有隐式生成的移动构造函数或移动赋值运算符。 (因此你的说法 "The e object is moved" 是错误的;更多内容见下文)。

e可复制,u不可复制。 e 被认为是 可移动的 因为如果没有移动构造函数,移动会回到复制。


根据 C++14 的 [except.throw] 部分(在 C++11 中也是如此),抛出异常时对象的初始化等同于:

e temp = e_;      // (1)
const e& _e = temp;  // (2)

对于(1),没有创建临时文件,因为e_temp具有相同的类型。

对于(2)是直接绑定:_e直接引用temp,没有再临时


您可能还记得 (1) 是一个 copy elision 上下文。这意味着必须存在可访问的复制或移动构造函数;但是允许编译器跳过对该构造函数的调用并为两个对象使用相同的内存space。

这可能就是你所说的 "The e object is moved" 的意思:你看到它没有被复制,但实际上它是复制省略。


综上所述:throw - catch 工作的唯一要求是 e 具有可调用的移动构造函数或复制构造函数。 (这个构造函数可能不会真正被调用,但它必须存在)。

你的 e 实际上有一个可调用的复制构造函数,所以代码是正确的,你尝试的 MSVC 版本有问题。

如果您仍然可以访问该版本,尝试实际编写 e temp = e_; const e& _e = temp; 并查看该代码是否生成相同的错误消息会很有趣。这将显示错误是否与编译器的 copy-initialization 实现有关,而不是与它的抛出有关。