15

我在 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&”类型的非常量引用的初始化无效</p>

我认为复制构造函数不必是 const,就我而言,我想修改其他参考中的某些内容。我知道非常量参数不能采用 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().

4

2 回答 2

17

问题是您没有移动构造函数。

当你请求std::vector某个emplace_back东西时,它必须确保它有足够的存储空间来构造新对象。如果需要,该例程的一部分是实例化一堆代码,元素从旧缓冲区移动到任何新分配的缓冲区。即使在运行时不会重新分配,该代码也将由模板实例化。

您的类有一个用户定义的复制构造函数,因此移动构造函数被隐式删除。因此,将原始缓冲区中的任何元素移动到新缓冲区中的尝试将通过重载决议转变为复制尝试。您对放置 new 的关注实际上是一条红鲱鱼,真正的问题在这个简单的例子中很明显:

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 规则。也要仔细考虑你的类的移动语义。

于 2017-10-14T11:47:15.100 回答
13

如果编译器给出内部错误,显然这是一个编译器错误。

emplace_back(7.f)使用构造函数Vertex(float pos)来放置对象——复制构造函数不直接参与。

错误的实际原因是不同的。通常,当您放置在向量中时,可能会发生重新分配。如果是,则必须将向量中的所有对象重新定位到内存中的新位置。

显然,是否发生重新分配是运行时条件。在运行时出现编译错误是不可行的;emplace_back因此,如果对象不支持重新分配,则该错误必须在编译时使用时发生;即使这个调用的向量恰好是空的。

标准术语可在 C++14 表 87 中找到:为了放置到向量中,元素类型必须是MoveInsertable和 MoveAssignable。

无需过多介绍,非常量复制构造函数和无移动构造函数的组合意味着对象无法满足 MoveInsertable 要求,因为所述要求中的右值参数不会绑定到非常量左值引用。

于 2017-10-14T11:44:46.820 回答