Переместить конструктор против копирования. Какой из них называется?

У меня есть два кода здесь, чтобы показать вам. Они представляют собой два класса, и каждый из них предоставляет Move Constructor и функцию, которая возвращает временную.

  • В первом случае функция, возвращающая временные вызовы, Move Constructor
  • Во втором случае функция, возвращающая временную информацию, просто сообщает компилятору выполнить копирование

Я запутался: в обоих случаях я определяю Move Constructor и случайную функцию-член, возвращающую временную. Но поведение меняется, и мой вопрос почему.

Обратите внимание, что в следующих примерах оператор < был перегружен, чтобы распечатать список (в первом случае) и двойной элемент данных (во втором случае).


ПЕРЕМЕЩЕННЫЙ КОНСТРУКТОР ПОЛУЧИТ

template<typename T>
class GList
{
public:
    GList() : il{ nullptr } {}

    GList(const T& val) : il{ new Link<T>{ val,nullptr } }  {}

    GList(const GList<T>& copy) {}

    GList(GList<T>&& move)
    {
        std::cout << "[List] Move constructor called" << std::endl;

        // ... code ...
    }

    // HERE IS THE FUNCTION WHICH RETURNS A TEMPORARY!
    GList<T> Reverse()
    {
        GList<T> result;

        if (result.il == nullptr)
            return *this;

        ...
        ...
        ...

        return result;
    }
};

int main()
{

   GList<int> mylist(1);

   mylist.push_head(0);

   cout << mylist.Reverse();

   return 0;
}

Вывод:

[List] Переместить конструктор с именем

0

1


ВЫПОЛНИТЬ КОПИРОВАННОЕ ИСПРАВЛЕНИЕ

class Notemplate
{
   double d;
public:
   Notemplate(double val)
   {
      d = val;
   }

   Notemplate(Notemplate&& move)
   {
       cout << "Move Constructor" << endl;
   }

   Notemplate(const Notemplate& copy)
   {
       cout << "Copy" << endl;
   }

   Notemplate Redouble()
   {
       Notemplate example{ d*2 };
       return example;
   }
};

int main()
{
   Notemplate my{3.14};

   cout << my.Redouble();

   return 0;
}

Вывод:

6,28


Я ожидал вызова Move Constructor во втором примере. В конце концов, логика для функции такая же: вернуть временную.

Кто-нибудь объяснит мне, почему это не происходит?

Как я могу справиться с копиями?

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

Ответы

Ответ 1

В комментариях другого SO-ответа ОП разъясняет, что он здесь задает:

Я слышал, что разрешение на копирование CAN может произойти, даже если их больше 1 возвращения. Я хотел бы знать, когда исключение копии запрещено

Итак, я пытаюсь решить эту проблему здесь:

Элинирование операций копирования/перемещения (называемое копированием по стандарту С++) допускается при следующих обстоятельствах:

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

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

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

  • Когда объявление исключения обработчика исключений объявляет объект того же типа (кроме cv-qualification) в качестве объекта исключения, операцию копирования можно опустить, рассматривая объявление исключения как псевдоним для объект исключения, если значение программы не изменится, за исключением выполнения конструкторов и деструкторов для объекта, объявленного объявлением исключения. Не может быть перемещение из объекта исключения, поскольку оно всегда является lvalue.

Копирование разрешений запрещено во всех других случаях.

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

С++ 17 Обновление

В настоящее время существует несколько мест, где копирование является обязательным. Если prvalue может быть привязано непосредственно к параметру функции по значению или по типу возвращаемого значения или к именованной локальной переменной, то копирование является обязательным в С++ 17. Это означает, что компилятор не должен беспокоиться даже о проверке копирования или перемещения конструктора. Legal С++ 17:

struct X
{
    X() = default;
    X(const X&) = delete;
    X& operator=(const X&) = delete;
};

X
foo(X)
{
    return X{};
}

int
main()
{
    X x = foo(X{});
}

Ответ 2

Эпиляция копирования - это оптимизация, которая в настоящее время обеспечивает каждый современный компилятор.

При возвращении огромных объектов класса в С++ этот метод применяется... , но не во всех случаях!

В первом примере компилятор выполняет Move Constructor, потому что у нас в функции более одного оператора return.