Помещение производного подвижного, но не копируемого в вектор дает ошибку компиляции
У меня возникают ошибки компилятора при попытке emplace_back
в векторе не копируемых, но подвижных объектов с тонким поворотом наследования, что, насколько мне известно, не должно изменить проблему.
Это законный C++ и это ошибка Visual Studio 2015, или я делаю очевидную ошибку?
#include <vector>
class Base
{
public:
Base() {}
Base(Base&) = delete;
Base& operator= (Base&) = delete;
};
class Test : public Base
{
public:
Test(int i) : m_i(i) {}
Test(Test&&) = default;
Test& operator= (Test&&) = default;
protected:
int m_i;
};
int main(int argc, char *argv[])
{
std::vector<Test> vec;
vec.emplace_back(1);
}
Выход:
error C2280: 'Test::Test(Test &)': attempting to reference a deleted function
Без наследования, то есть с конструктором удаленных копий в Test и без базового класса, он компилируется правильно.
Каким-то образом, удаление значения по умолчанию в конструкторе перемещения также заставляет его корректно компилироваться, но тогда мне нужно определить конструктор перемещения, и я не хочу туда идти.
Что означает, что это хорошо компилируется:
#include <vector>
class Test
{
public:
Test(int i) : m_i(i) {}
Test(Test&) = delete;
Test& operator= (Test&) = delete;
Test(Test&&) = default;
Test& operator= (Test&&) = default;
protected:
int m_i;
};
int main(int argc, char *argv[])
{
std::vector<Test> vec;
vec.emplace_back(1);
}
Загадочный?
1 ответ
Компилятор корректен во всех описанных вами случаях.
когда Test
происходит от Base
его конструктор перемещения по умолчанию определен как удаленный, потому что он пытается переместить Base
, который не может быть сконструирован. Test
на самом деле не является движущейся конструкцией в вашем первом примере.
Во втором примере без базового класса ничто не мешает определить конструктор перемещения по умолчанию, поэтому Test
становится перемещаемым.
Когда вы предоставляете определение для конструктора перемещения, вам решать Base
, Если вы просто напишите
Test(Test&&) { }
это будет просто построить по умолчанию Base
объект, так что конструктор перемещения скомпилируется, но, вероятно, не будет делать то, что вы хотите.
Base
не является move-constructible, потому что он имеет объявленный пользователем конструктор копирования, который предотвращает неявное объявление конструктора перемещения - у него вообще нет конструктора перемещения - и его конструктор копирования удаляется (он все равно не может обработать значения r, потому что он принимает неконстантная ссылка).
Если вы делаете Base
двигаться-конструируем, добавляя, например,
Base(Base&&) = default;
затем Test
также становится перемещаемым, и ваш пример будет компилироваться.
Последний кусок головоломки: так как мы объявили конструктор перемещения для Test
Почему сообщение об ошибке ссылается на конструктор удаленной копии, даже в первом случае?
Для объяснения std::vector
Логика выбора конструктора для копирования / перемещения элементов во время перераспределения, см. этот ответ. Если посмотреть на логику, которую использует std::move_if_noexcept, чтобы выбрать тип возвращаемой ссылки, в нашем случае это будет ссылка на значение (когда T
не копируемо, условие всегда ложно). Таким образом, мы все еще ожидаем, что компилятор попытается вызвать конструктор перемещения.
Однако в игру вступает еще одно правило: конструктор перемещения по умолчанию, который определен как удаленный, не участвует в разрешении перегрузки.
Это сделано для того, чтобы конструкция из rvalue могла вернуться к конструктору копирования, принимающему const
Ссылка на значение, если доступно. Обратите внимание, что правило не применяется, когда конструктор перемещения явно объявлен как удаленный.