什么是这个奇怪的复制构造错误抱怨?

时间:2017-10-14 11:15:55

标签: c++ c++11 copy-constructor const-reference

我在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中编译,它给出的唯一错误是:

  

&#34;编译器&#34;

中发生了内部错误

如果我启用语言扩展,它编译得很好。如果我保持语言扩展名禁用并使复制构造函数采用const Vertex&它编译正常。

所以我在一些在线编译器上尝试了GCC,如果复制构造函数没有采用const引用参数,它就不会编译,从而产生各种错误。似乎最有意义的是:

  

错误:“Vertex&amp;”类型的非const引用的初始化无效   来自'Vertex'类型的右值

我认为复制构造函数不必是const,在我的情况下,我想修改另一个引用中的内容。我知道非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

}

所以我不知道发生了什么。我想首先知道为什么如果没有调用复制构造函数会发生这种情况,以及在这种情况下是否有一种方法可以在我的复制构造函数中使用非const,即使用{ {1}},因为似乎只有使用vector::emplace_back()才会出现此问题。

2 个答案:

答案 0 :(得分:18)

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

当您向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的规则。仔细考虑为你的类移动语义。

答案 1 :(得分:13)

显然,如果编译器发出内部错误,那就是编译器错误。

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

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

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

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

没有太多细节,非const复制构造函数和没有move-constructor的组合意味着该对象失败了MoveInsertable要求,因为所述要求中的rvalue参数不会绑定到非const左值参考。