Почему неявно и явно удаленные конструкторы перемещения обрабатываются по-разному?
Что такое обоснование за разным рассмотрением неявно и явно удаленных конструкторов перемещения в стандарте С++ 11 в отношении неявной генерации конструкторов перемещения содержащих/наследующих классов?
Делает ли С++ 14/С++ 17 что-либо менять? (За исключением DR1402 в С++ 14)
Примечание: я понимаю, что происходит, я понимаю, что это согласно стандартным правилам С++ 11, меня интересует обоснование этих правил, которые подразумевают это поведение (пожалуйста, не просто повторите, что это так оно и есть, потому что стандарт говорит так).
Предположим, что класс ExplicitDelete
с явно удаленным движением ctor и явно дефолтной копией ctor. Этот класс не является move constructible
, хотя совместимая копия ctor доступна, потому что разрешение перегрузки выбирает конструктор перемещения и выходит из строя во время компиляции из-за его удаления.
Предположим, что класс ImplicitDelete
содержит или наследует от ExplicitDelete
и ничего не делает. Этот класс будет иметь свое движение, которое неявно объявляется удаленным из-за С++ 11 move ctor rules. Однако этот класс по-прежнему будет move constructible
через его копию ctor. (Это последнее утверждение связано с разрешением DR1402?)
Тогда класс Implicit
, содержащий/наследующий от ImplicitDelete
, будет иметь совершенно прекрасный неявный конструктор перемещения, сгенерированный, который вызывает ImplicitDelete
copy ctor.
Итак, в чем же причина, позволяющая Implicit
иметь возможность двигаться неявно и ImplicitDelete
не иметь возможности двигаться неявно?
На практике, если Implicit
и ImplicitDelete
имеют некоторые сверхмощные подвижные элементы (думаю, vector<string>
), я не вижу причин, чтобы Implicit
был значительно превосходен для ImplicitDelete
в движении. ImplicitDelete
все еще может копировать ExplicitDelete
из своего неявного перемещения ctor — точно так же, как Implicit
делает с ImplicitDelete
.
Для меня это поведение кажется непоследовательным. Я бы счел это более последовательным, если бы произошла одна из этих двух вещей:
-
Компилятор обрабатывает как неявно, так и явно удаленные переменные перемещения:
-
ImplicitDelete
становится не move-constructible
, как ExplicitDelete
-
ImplicitDelete
удаленное перемещение ctor приводит к удаленному неявному перемещению ctor в Implicit
(таким же образом, что ExplicitDelete
делает это с ImplicitDelete
)
-
Implicit
становится не move-constructible
- Компиляция строки
std::move
полностью не работает в моем примере кода
-
Или компилятор возвращается к копированию ctor также для ExplicitDelete
:
-
ExplicitDelete
Конструктор копирования вызывается во всех move
s, как и для ImplicitDelete
-
ImplicitDelete
получает правильное неявное перемещение ctor
- (
Implicit
в этом сценарии не изменяется)
- Вывод образца кода указывает, что элемент
Explicit
всегда перемещается.
Вот полный рабочий пример:
#include <utility>
#include <iostream>
using namespace std;
struct Explicit {
// prints whether the containing class move or copy constructor was called
// in practice this would be the expensive vector<string>
string owner;
Explicit(string owner) : owner(owner) {};
Explicit(const Explicit& o) { cout << o.owner << " is actually copying\n"; }
Explicit(Explicit&& o) noexcept { cout << o.owner << " is moving\n"; }
};
struct ExplicitDelete {
ExplicitDelete() = default;
ExplicitDelete(const ExplicitDelete&) = default;
ExplicitDelete(ExplicitDelete&&) noexcept = delete;
};
struct ImplicitDelete : ExplicitDelete {
Explicit exp{"ImplicitDelete"};
};
struct Implicit : ImplicitDelete {
Explicit exp{"Implicit"};
};
int main() {
ImplicitDelete id1;
ImplicitDelete id2(move(id1)); // expect copy call
Implicit i1;
Implicit i2(move(i1)); // expect 1x ImplicitDelete copy and 1x Implicit move
return 0;
}
Ответы
Ответ 1
Итак, в чем же причина, по которой Implicit может двигаться неявно и ImplicitDelete не может двигаться неявно?
Обоснование будет таким: случай, который вы описываете, не имеет смысла.
Смотрите, все это началось из-за ExplicitDelete
. По вашему определению этот класс имеет явно удаленный конструктор перемещения, но конструктор копии по умолчанию.
Существуют неподвижные типы, без копирования и перемещения. Существуют типы только для перемещения. И есть типы для копирования.
Но тип, который можно скопировать, но имеет явно удаленный конструктор перемещения? Я бы сказал, что такой класс является противоречием.
Вот три факта, как я вижу:
-
Явное удаление конструктора перемещения подразумевает, что вы не можете его перемещать.
-
Очевидно, что по умолчанию конструктор копирования должен означать, что вы можете его скопировать (для целей этого разговора, конечно. Я знаю, что вы все равно можете делать то, что делает явным по умолчанию удаленным).
-
Если тип можно скопировать, его можно перенести. Вот почему существует правило о неявно удаленных конструкторах перемещения, не участвующих в разрешении перегрузки. Следовательно, движение является правильным подмножеством копирования.
Поведение С++ в этом случае противоречиво, потому что ваш код противоречив. Вы хотите, чтобы ваш тип был скопирован, но не был перемещаемым; С++ не позволяет этого, поэтому он ведет себя странно.
Посмотрите, что происходит, когда вы удаляете противоречие. Если вы явно удаляете конструктор копирования в ExplicitDelete
, все имеет смысл снова. Конструкторы copy/move ImplicitDelete
неявно удаляются, поэтому они неподвижны. И конструкторы Implicit
copy/move неявно удаляются, поэтому он тоже неподвижен.
Если вы пишете противоречивый код, С++ не будет вести себя совершенно законным образом.