【问题标题】:Throwing copyable class deriving from noncopyable抛出派生自不可复制的可复制类
【发布时间】:2015-06-16 08:18:15
【问题描述】:

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

这适用于 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 &amp;)' : attempting to reference a deleted function

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

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

顺便说一句,如果u 复制函数未被删除(也未定义,c++11 之前的版本不可复制),MSVC 在u::u(const u&amp;) 查找期间在链接阶段失败。 (未解决的外部)

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

【问题讨论】:

  • 这适用于 webcompiler.cloudapp.net,所以我将使用 MSVC 2015 中修复的 MSVC 错误。
  • 似乎是 msvc 错误,不调用移动构造函数:stackoverflow.com/questions/21715468/can-i-throw-a-unique-ptr
  • 我的第一个猜测是,当抛出 e 对象时,您可能会将其捕获为 u。 (不作为对u 的引用。)在这种情况下,您将需要u 的复制ctor。也许 gcc 和 clang 将该复制调用放在 catch 子句附近(此处不存在),但 MSVC 可能希望将其放在 throw 语句之后。它不会被调用,但编译器会引用它。
  • u 是一个异常类,可以有效地禁用 C++11 异常处理机制。复制构造函数的 const u&amp; 返回大声而明确地表明这不是故意的:开发人员无能。因此,简单的解决方案是:放弃该库。也就是说,MSVC 有许多与构造函数相关的错误,但虽然它在法律上不属于它的权利,但它在道德上是正确的,因为你已经告诉它 e IS-A u没有添加数据。因此,将继承设置为非public 会使代码编译,但却违背了目的。 :(

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


【解决方案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 有一个可调用的移动构造函数或复制构造函数。 (这个构造函数实际上可能没有被调用,但它必须存在)。

您的 e 实际上确实有一个可调用的复制构造函数,因此代码是正确的,并且您尝试的 MSVC 版本存在错误。

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

【讨论】:

  • 我仍然可以使用这个版本的编译器。 e temp = e_; const e&amp; _e = temp; 代码编译时没有错误。尝试抛出任何这些变量都会失败。
【解决方案2】:

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

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

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

您似乎无法以适当的方式处理此问题;任何带有指针的魔法都会导致不正确的对象删除。

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

【讨论】:

  • 对不起,我喝醉了)我已经修复了我的样本
猜你喜欢
  • 2011-10-15
  • 1970-01-01
  • 2015-05-30
  • 1970-01-01
  • 2013-02-11
  • 2012-05-20
  • 2021-08-25
  • 2013-10-04
  • 1970-01-01
相关资源
最近更新 更多