Последовательные вызовы для перемещения конструктора при компиляции с -fno-ellide-constructors - PullRequest
2 голосов
/ 17 марта 2020

В следующем коде (сборка на g cc 9.2 с -std=c++14 -Wall -fno-elide-constructors):


struct Noisy {
    Noisy() { std::cout << "Default construct [" << (void*)this << "]\n"; }
    Noisy(const Noisy&) { std::cout << "Copy construct [" << (void*)this << "]\n"; }
    Noisy(Noisy&&) { std::cout << "Move construct [" << (void*)this << "]\n"; }
    Noisy& operator=(const Noisy&) { std::cout << "Copy assignment" << std::endl; return *this; }
    Noisy& operator=(Noisy&&) { std::cout << "Move assignment" << std::endl; return *this; }
    ~Noisy() { std::cout << "Destructor [" << (void*)this << "]\n"; }
};

Noisy f() {
    Noisy x;
    return x;
}

Noisy g(Noisy y) {
    return y;
}
int main(void) {
    Noisy a;
    std::cout << "--- f() ---\n";
    Noisy b = f();
    std::cout << "b [" << (void*)&b << "]\n";
    std::cout << "--- g(a) ---\n";
    Noisy c = g(a);
    std::cout << "c [" << (void*)&c << "]\n";
    std::cout << "---\n";
    return 0;
}

, который приводит к такому результату:

Default construct [0x7ffc4445737a]
--- f() ---
Default construct [0x7ffc4445735f]
Move construct [0x7ffc4445737c]
Destructor [0x7ffc4445735f]
Move construct [0x7ffc4445737b]
Destructor [0x7ffc4445737c]
b [0x7ffc4445737b]
--- g(a) ---
Copy construct [0x7ffc4445737e]
Move construct [0x7ffc4445737f]
Move construct [0x7ffc4445737d]
Destructor [0x7ffc4445737f]
Destructor [0x7ffc4445737e]
c [0x7ffc4445737d]
---
Destructor [0x7ffc4445737d]
Destructor [0x7ffc4445737b]
Destructor [0x7ffc4445737a]

Почему копия локальный объект с шумом [0x7ffc4445735f] в f() разрушается сразу после перемещения в адрес возврата f (и до начала строительства b); в то время как то же самое, похоже, не происходит для g()? Т.е. в последнем случае (при выполнении g()) локальная копия аргумента функции Noisy y, [0x7ffc4445737e] уничтожается только после того, как c готово к построению. Разве он не должен был быть уничтожен сразу после перемещения на обратный адрес g, как это случилось с f()?

Ответы [ 2 ]

2 голосов
/ 17 марта 2020

Это переменные для адресов в выходных данных:

0x7ffc4445737a  a
0x7ffc4445735f  x
0x7ffc4445737c  return value of f() 
0x7ffc4445737b  b
0x7ffc4445737e  y
0x7ffc4445737f  return value of g()
0x7ffc4445737d  c

Я интерпретирую вопрос как: вы выделяете следующие две точки:

  • x is уничтожено до того, как b построено
  • y уничтожено после того, как c построено

и спросите, почему оба случая не ведут себя одинаково.


Ответ таков: в C ++ 14 стандарт, указанный в [expr.call] / 4, что y должен быть уничтожен при возврате функции. Однако не было четко указано, на каком этапе возврата функции это означает. Возникла проблема с CWG.

Начиная с C ++ 17, спецификация теперь такова, что она определяется реализацией, уничтожается ли y одновременно с локальными переменными функции или в конце полное выражение, содержащее вызов функции. Оказалось, что два случая не могут быть согласованы, потому что это будет серьезное изменение ABI (подумайте, что произойдет, если деструктор y сгенерирует исключение); а также Itanium C ++ ABI определяет разрушение в конце полного выражения.

Мы не можем однозначно сказать, что g++ -std=c++14 не соответствует C ++ 14 из-за неоднозначности C + +14 формулировка, однако в любом случае она не изменится сейчас из-за проблемы ABI.

Для объяснения со ссылками на Стандарт и отчет CWG см. Этот вопрос: Последовательность функций уничтожение параметров , а также Позднее уничтожение параметров функции

1 голос
/ 17 марта 2020

Разница довольно очевидна, если вы посмотрите на сгенерированную сборку (например, в проводнике компилятора ).

Здесь вы можете видеть, что для вызова g аргумент объект фактически создается и уничтожается в функции main.

Так что для функции g порядок вывода будет

  1. Копирование-конструкция аргумента y из a
  2. Вызов функции g, передача y
  3. Внутри функции g, y перемещается во временный возвращаемый объект
  4. Функция g возвращает
  5. Назад в main объект временного возврата перемещен в c
  6. Объект временного возврата уничтожен
  7. Объект аргумента y destructed

Для функции f локальный объект x создается и разрушается в области действия f:

  1. f называется
  2. x построен по умолчанию
  3. Временный объект возврата построено на основе перемещения из x
  4. x уничтожено
  5. Функция f возвращает
  6. Временный объект возврата перемещен в b
  7. Временный объект возврата уничтожен
...