Бросать подвижные предметы - PullRequest
15 голосов
/ 15 июня 2011

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

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

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

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

#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 via 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, независимо от настроек оптимизации, результаты одинаковы за исключением того, что два хода являются копиями.Это та разница, которая первоначально привлекла мое внимание.

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

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

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

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

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

Ответы [ 2 ]

8 голосов
/ 15 июня 2011

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

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

...

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

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

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

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

  1. Сначала представьте, что lvalue является rvalue при принятии решения о том, какой конструктор будет вызван или исключен.

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

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

  1. Если вы можете отказаться от строительства, сделайте это.
  2. В противном случае, если вы можете переместить объект, сделайте это.
  3. В противном случае, если вы можете скопировать объект, сделайте это.
  4. Иначе испускают диагностикуic.

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

3 голосов
/ 15 июня 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...