На что жалуется эта странная ошибка конструктора копирования?

Я нахожусь на Visual Studio 2017. Недавно, потому что мне не нравились несоответствующие стандарты C++, я пошел дальше и отключил нестандартные языковые расширения в опциях. Все идет нормально. Теперь у меня проблема.

#include <iostream>
#include <vector>


struct Vertex
{
    Vertex(float pos) { }
    Vertex(Vertex& other) { }
};

std::vector<Vertex> arrayOfVertices;

int main()
{
    arrayOfVertices.emplace_back(7.f);
}

Это не скомпилирует в Visual Studio, единственная ошибка, которую это дает:

"произошла внутренняя ошибка в компиляторе"

Если я включаю языковые расширения, то это прекрасно компилируется. Если я оставлю языковые расширения отключенными и заставлю конструктор копирования взять const Vertex& он компилируется нормально.

Поэтому я попробовал GCC на некоторых онлайн-компиляторах, и если конструктор копирования не принимает аргумент ссылки на const, он не скомпилируется, что приводит к различным ошибкам. Тот, который, казалось, имел больше всего смысла:

ошибка: неверная инициализация неконстантной ссылки типа 'Vertex&' из значения типа 'Vertex'

Я думал, что конструкторы копирования не должны быть константными, в моем случае я хотел бы изменить что-то в другой ссылке. Я знаю, что неконстантные аргументы не могут принимать ссылки на r-значения, но я проверил это, и оказалось, что в vector::emplace_back() конструктор копирования вообще не вызывается:

#include <iostream>
#include <vector>

struct Vertex
{
    Vertex(float pos) 
    { 
        std::cout << "Calling constructor\n";
    }
    Vertex(const Vertex& other) 
    { 
        std::cout << "Calling copy constructor\n";
    }
};

std::vector<Vertex> arrayOfVertices;

int main()
{
    arrayOfVertices.emplace_back(7.f); // Normal constructor called if const,
                                       // doesn't compile if non-const

    auto buff = malloc(sizeof(Vertex)); // Placement new
    new (buff) Vertex(7.f); // Normal constructor called whether const 
                            // or non-const. This is what I thought emplace_back did

}

Поэтому я понятия не имею, что происходит. Я хотел бы знать, во-первых, почему это происходит, если конструктор копирования не вызывается, а также если есть способ взять неконстантный в моем конструкторе копирования в этом случае, то есть, используя vector::emplace_back()потому что кажется эта проблема возникает только при использовании vector::emplace_back(),

2 ответа

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

Когда вы запрашиваете std::vector в emplace_back что-то, он должен убедиться, что у него достаточно памяти для создания нового объекта. Частью этой подпрограммы является создание пакета кода, который перемещает элементы из старого буфера в любой вновь выделенный буфер, если это требуется. Этот код будет создан шаблоном, даже если во время выполнения перераспределения не будет.

Ваш класс имеет определяемый пользователем конструктор копирования, поэтому конструктор перемещения неявно удаляется. Таким образом, попытка переместить любой элемент в исходном буфере в новый будет преобразована в попытку копирования с помощью разрешения перегрузки. Ваше внимание на размещение новых является на самом деле красная сельдь, реальная проблема очевидна в этом простом примере:

Vertex v1{7.f},
       v2{std::move(v1)};
       // Error, the xvalue from `move` can't bind to a non-const reference

Вы можете довольно легко отключить ошибку, вернув конструктор перемещения, например, явным образом установив его по умолчанию:

struct Vertex
{
    Vertex(float) 
    { 
        std::cout << "Calling constructor\n";
    }

    Vertex(Vertex&&) = default;

    Vertex(Vertex&) 
    { 
        std::cout << "Calling copy constructor\n";
    }
};

Никогда не забывайте, что в C++11 правило 0/3 стало правилом 0/3/5. Тщательно продумайте семантику перемещения для своих классов.

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

emplace_back(7.f) использует конструктор Vertex(float pos) для размещения объекта - конструктор копирования не участвует напрямую.

Фактическая причина ошибки другая. Как правило, при встраивании в вектор может происходить перераспределение. Если это так, то все объекты в векторе должны быть перемещены в новое место в памяти.

Ясно, что это условие времени выполнения относительно того, происходит ли перераспределение. Невозможно иметь ошибку компиляции во время выполнения; поэтому ошибка должна возникать при использовании во время компиляции emplace_back если объекты не поддерживают перераспределение; даже если вектор окажется пустым для этого вызова.

Стандартная терминология найдена в C++. 14 Таблица 87: для включения в вектор тип элемента должен быть MoveInsertable и MoveAssignable.

Не вдаваясь в подробности, комбинация неконстантного конструктора копирования и отсутствие конструктора перемещения означает, что объект не соответствует требованию MoveInsertable, поскольку аргумент rvalue в указанном требовании не будет привязываться к неконстантной ссылке на lvalue.

Другие вопросы по тегам