Какую ошибку вызывает ошибка конструктора?

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

#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 в некоторых онлайн-компиляторах, и если конструктор копирования не принимает аргумент ссылки на константу, он не будет компилироваться, предоставляя различные ошибки. Тот, который, казалось, имел наибольший смысл:

ошибка: недействительная инициализация неконстантной ссылки типа "Vertex & из rvalue типа" Vertex

Я думал, что конструкторы копирования не обязательно должны быть const, в моем случае я хотел бы что-то изменить в другой ссылке. Я знаю, что аргументы non-const не могут принимать ссылки r-value, но я тестировал их, и получается, что в 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().

Ответы

Ответ 1

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

Когда вы запрашиваете std::vector to 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";
    }
};

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

Ответ 2

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

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

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

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

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

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