抛出来自noncopyable的可复制类

时间:2015-04-10 14:45:21

标签: c++ c++11 visual-c++

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

这适用于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不是复制构造的,它们都不会编译

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

BTW,如果u复制函数未被删除(也未定义,pre-c ++ 11不可复制),则MSVC在u::u(const u&)查找期间的链接阶段失败。 (未解决的外部)

我的代码中是否存在缺陷,或MSVC中存在此错误?

2 个答案:

答案 0 :(得分:1)

首先,eu都有用户声明的复制构造函数。这意味着它们没有隐式生成的移动构造函数或移动赋值运算符。 (因此,您的声明“e对象被移动”是错误的;更多内容如下所示)。

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上下文。这意味着必须存在可访问的副本或移动构造函数;但是允许编译器跳过对该构造函数的调用,并为这两个对象使用相同的内存空间。

这可能就是你所说的“e对象被移动”的意思:你看到它没有被复制,但是当它实际上是复制省略时就采取了行动。

全部放在一起:throw - catch工作的唯一要求是e具有可调用的move-constructor或copy-constructor。 (实际上可能不会调用此构造函数,但它必须存在)。

你的e确实有一个可调用的拷贝构造函数,所以代码是正确的,并且你试过的MSVC版本被窃听了。

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

答案 1 :(得分:-1)

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

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

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

似乎你无法以正确的方式处理这个问题;任何带指针的魔法都会导致不正确的对象删除。

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