Почему здесь называется конструктор перемещения?

Вот пример кода из викторины С++:

#include <iostream>
struct X {
    X(const char *) { std::cout << 1; }
    X(const X &) { std::cout << 2; }
    X(X &&) { std::cout << 3; }
};

X f(X a) {
    return a;
}

X g(const char * b) {
    X c(b);
    return c;
}

int main() {
    f("hello");
    g("hello");
}

Каким будет выход программы?

Я так думаю:

  • f(X a) вызывается, и конструктор неявно преобразует const char* в X, поэтому вывод 1
  • Поскольку у нас нет объекта для хранения возвращаемого значения, возвращаемое значение отбрасывается, нет вывода
  • g(const char*) вызывается, а X c(b) X(const char*) Выход 1
  • Возвращаемое значение еще раз отбрасывается - без вывода

Итак, ответ 11. Ответ, который дается викторине, равен 131. Ответ, который я получаю с g++ 4.4.4-13, - 121.

Говорят, что этот код был скомпилирован с помощью этой команды:

g++ -std=c++11 -Wall -Wextra -O -pthread

Откуда взялось среднее число? И почему это может быть 3 или 2?

Ответы

Ответ 1

В теории это может печатать любые из 131, 13313, 1313 и 1331. Это довольно глупо, как вопрос викторины.

  • f("hello");:

    • "hello" преобразуется во временный X через конструктор преобразования, печатает 1.
    • Временная X используется для инициализации аргумента функции, вызывает конструктор перемещения, печатает 3. Это можно устранить.
    • X используется для инициализации временного возвращаемого значения, вызывает конструктор перемещения, печатает 3. Это параметр функции, поэтому разрешение не разрешено, но возврат является неявным перемещением.
  • g("hello");

    • "hello" используется для построения c через конструктор преобразования, выводит 1.
    • c используется для инициализации временного возвращаемого значения, вызывает конструктор перемещения, печатает 3. Это можно устранить.

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

Что касается печати 2, то из-за того, что используемый вами древний компилятор не реализует правило implicit-move-when-return-a-local-variable.

Ответ 2

Копирование elision применяется к оператору return в g и, возможно, в другом месте. Цитируется из cppreference:

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

Итак, к вашему примеру кода, вывод не может быть надежно предсказан в разных реализациях.