Выбрасывание класса с возможностью копирования из не подлежащего копированию

У меня есть фреймворк, который определяет исключение как класс, не подлежащий копированию, из которого мы вывели класс для копирования (определяющий конструктор копирования, вызывающий конструктор базового класса без копирования)

Это работает под 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 жалуется на Error 1 error C2280: 'u::u(const u &)' : attempting to reference a deleted function в конце функции foo().

g++ и clang как скомпилировать код, так и вообще не использовать конструктор копирования (объект e перемещен), но не будет компилироваться, если e не является конструктивным для копирования.

EDIT: я редактировал код для принудительной копии.

BTW, если функции копирования u не удаляются (и не определены, pre-С++ 11 не копируются), MSVC выходит из строя на этапе ссылки во время поиска u::u(const u&). (неразрешенный внешний)

Есть ли недостаток в моем коде, или это ошибка в MSVC?

Ответы

Ответ 1

Во-первых, e и u оба имеют объявленные пользователем копии-конструкторы. Это означает, что у них нет неявно созданного оператора move-move или оператора присваивания. (Поэтому ваше требование "Объект e перемещен" является ложным, более подробно об этом ниже).

e является скопируемым, u не копируется. e считается подвижным, потому что перемещение возвращается к копированию, если нет конструктора move.


В соответствии с разделом [except.throw] С++ 14 (это было то же самое в С++ 11), инициализация объектов при генерации исключения эквивалентна:

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

Для (1) временного не создается, потому что e_ и temp имеют один и тот же тип.

Для (2) это прямая привязка: _e ссылается на temp напрямую, и нет временного повторения.


Вы можете вспомнить, что (1) является контекстом copy elision. Это означает, что должен существовать доступный экземпляр или перемещать конструктор; но компилятору разрешено пропустить вызов этого конструктора и использовать одно и то же пространство памяти для обоих объектов.

Это, вероятно, то, что вы имели в виду, говоря: "Объект e перемещен": вы видели, что он не был скопирован, но принял ход, когда на самом деле это было копирование.


Объединяя все это: единственное требование для работы throw - catch состоит в том, что e имеет вызываемый конструктор move или конструктор-копию. (Этот конструктор фактически не может быть вызван, но он должен существовать).

У вашего e действительно есть вызывающий экземпляр-конструктор, поэтому код правильный, и версия MSVC, которую вы пробовали, прослушивалась.

Если у вас все еще есть доступ к этой версии, было бы интересно попробовать написать e temp = e_; const e& _e = temp; и посмотреть, генерирует ли этот код такое же сообщение об ошибке. Это покажет, является ли ошибка с реализацией экземпляра copy-initialization компилятором, а не ошибкой с его метанием.

Ответ 2

Для MSVC 2013 действительно требуются классы с возможностью копирования. Кроме того, его конструктор должен быть явным. Следующий код неверен для MSVC 2013:

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

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

Кажется, что вы не сможете справиться с этой проблемой должным образом; любая магия с указателями приведет к удалению неправильных объектов.

Я вижу только одно решение: сообщите разработчикам фреймворка о своей проблеме.