Бросание подвижных объектов

Я заметил небольшое несоответствие в том, как MSVC и g++ обрабатывают создание временного объекта исключения, когда брошенный тип является подвижным. Охота на них подняла дополнительные вопросы.

Прежде чем идти дальше, вот в чем суть моего вопроса: в отсутствие копирования/перемещения elision, кто хорошо делает стандарт, скажите, как создать объект временного исключения? На данный момент лучшим, что я смог сделать, является следующая цитата: от 15.1/3:

Выражение-выражение инициализирует временный объект, называемый объектом исключения, тип которого определяется удалением всех cv-квалификаторов верхнего уровня из статического типа операнда броска и корректировки типа из массива T "или" функция возвращает T "в" указатель на T "или" указатель на функцию возврата T "соответственно.

Я предполагаю, что ответ похоронен где-то на другом языке, который определяет тип выражения и как инициализируются объекты, но мне не повезло собрать все это вместе. Когда объект генерируется, создается ли объект исключения get (a), (b) перемещается, если это необходимо, и копируется в противном случае или (c) инициализируется в реализации определенным образом?

Рассмотрим следующий код:

#include <iostream>
using std::cout;
using std::cin;
using std::endl;

struct Blob {
  Blob() { cout << "C" << endl; }
  Blob(const Blob&) { cout << "c" << endl; }
  Blob(Blob&&) { cout << "m" << endl; }
  Blob& operator =(const Blob&) { cout << "=" << endl; return *this; }
  Blob& operator =(Blob&&) { cout << "=m" << endl; return *this; }
  ~Blob() { cout << "~" << endl; }

  int i;
};

int main() {
  try {
     cout << "Throw directly: " << endl;
     throw Blob();
  } catch(const Blob& e) { cout << "caught: " << &e << endl; }
  try {
     cout << "Throw with object about to die anyhow" << endl;
     Blob b;
     throw b;
  } catch(const Blob& e) { cout << "caught: " << &e << endl;  }
  {
    cout << "Throw with object not about to die anyhow (enter non-zero integer)" << endl;
    Blob b;
    int tmp;
    cin >> tmp; //Just trying to keep optimizers from removing dead code
    try {
      if(tmp) throw b;
      cout << "Test is worthless if you enter '0' silly" << endl;
    } catch(const Blob& e) { cout << "caught: " << &e << endl;  }
    b.i = tmp;
    cout << b.i << endl;
  }
}

Это все воссоздано на ideone. Как вы можете [надеяться] видеть, gcc через ideone создает объект Blob на месте в первом случае и перемещается во втором. Результаты суммируются ниже, при этом значения указателя заменяются идентификаторами.

Throw directly: 
C {A}
caught: {A}
~ {A}
Throw with object about to die anyhow
C {A}
m {B} <- {A}
~ {A}
caught: {B}
~ {B}
Throw with object not about to die anyhow (enter non-zero integer)
C {A}
m {B} <- {A}
caught: {B}
~ {B}
2
~ {A}

Тот же код в MSVC2010, независимо от настроек оптимизации, результаты те же, за исключением двух перемещений - это копии. В этом и заключается разница, которая изначально попалась мне на глаза.

Первый тест, который я предполагаю, хорош; его классическое копирование.

Во втором тесте gcc ведет себя так, как я ожидал. Временной Blob обрабатывается как значение x, а объект исключения - это перемещение, построенное из него. Но я не уверен, что компилятор должен признать, что исходный Blob истекает; если это не так, то MSVC действует правильно при копировании. Таким образом, мой первоначальный вопрос: соответствует ли стандартный мандат тому, что здесь происходит, или это просто часть реализации, определенного для обработки исключений?

Третий тест в точности противоположный: MSVC ведет себя так, как того требует моя интуиция. gcc хочет перейти от b, но b все еще жив, о чем свидетельствует тот факт, что я продолжаю работать с ним после обработки выброшенного исключения. Очевидно, что в этом тривиальном примере перемещение или копирование не имеет никакого значения для самого b, но, конечно, компилятору не разрешено рассматривать это при рассмотрении разрешения перегрузки.

Очевидно, что наличие экземпляра copy/move elision делает этот простой тест трудным для обобщения, но большая проблема заключается в том, что либо компилятор может быть не совсем удовлетворительным еще [особенно в случае gcc с третьим тестом и MSVC в целом],

Обратите внимание, что это целиком для академических целей; Я почти никогда не бросаю ничего, кроме временного, которое оба компилятора создают на месте, и я вполне уверен, что поведение разрешено.

Ответы

Ответ 1

Поведение перехода соответствует случаю 2, но не к случаю 3. См. 12.8 [class.copy]/p31:

Когда выполняются определенные критерии, реализации разрешено опустить копировать/перемещать строительство класса объект, даже если копия/перемещение конструктор и/или деструктор для объект имеет побочные эффекты....

...

  • в выражении throw, когда операнд является именем энергонезависимого автоматический объект (кроме функция или параметр catch-clause) объем которого не выходит за рамки конец самого внутреннего охватывающего try-block (если есть), копировать/перемещать операцию из операнда к объекту исключения (15.1) может быть опущено путем создания автоматического объект непосредственно в исключение Объект

Вышеописанное не определяет, когда объект может быть неявно перемещен. Но определяет, когда копирование/перемещение разрешено. Чтобы получить, когда неявное перемещение законно, вы должны перейти к пункту 32 в том же разделе:

32 Когда критерии элиминации операции копирования выполняются или выполняются за исключением того факта, что источник объект является параметром функции, и подлежащий копированию объект по lvalue, разрешению перегрузки...

В этом параграфе объясняется, что когда копирование/перемещение разрешено законно, тогда разрешение перегрузки происходит с двумя проходами:

  • Сначала сделайте вид, что lvalue является rvalue при определении того, какой конструктор будет вызван или удален.

  • Если 1) терпит неудачу, повторите разрешение перегрузки с аргументом как lvalue.

Это приводит к созданию иерархии семантики перемещения от лучшего к худшему:

  • Если вы можете завершить строительство, сделайте это.
  • Если вы можете переместить объект, сделайте это.
  • Если вы можете скопировать объект, сделайте это.
  • Else испускает диагностику.

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

Ответ 2

Бросок - это очень реалистичное поведение. В С++ 03 исключение было скопировано определенное количество раз, помещенное в место, зависящее от реализации, упомянутое во время блока catch, а затем уничтоженное. В С++ 0x я ожидаю, что реализация либо будет иметь право как копировать, так и перемещать ее столько раз, сколько ей нравится, либо перемещать ее столько раз, сколько ей нравится (т.е. Вы можете бросать классы, не подлежащие копированию).

Однако, конечно, это не позволило вам получить доступ к объекту, который был перемещен из catch, так как это было бы Really Bad ™. Если вы это сделали, то это ошибка компилятора. Вы должны точно указать адрес объекта.

Что вы должны помнить, так это то, что реализация MSVC - это правила, которые существовали много лет назад, тогда как реализация GCC ralsues намного более поздняя. Правила, возможно, изменились с тех пор, как MSVC реализовал их. Тем не менее, компилятор будет ошибочен при попытке бросить класс, не подлежащий копированию, и предложит мне, чтобы компилятор мог свободно копировать и перемещать объект исключения.