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

Zeb*_*ish 15 c++ copy-constructor const-reference c++11

我在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);
}
Run Code Online (Sandbox Code Playgroud)

这不会在Visual Studio中编译,它给出的唯一错误是:

"编译器中发生内部错误"

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

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

错误:从'Vertex'类型的右值开始无效初始化'Vertex&'类型的非const引用

我认为复制构造函数不必是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

}
Run Code Online (Sandbox Code Playgroud)

所以我不知道发生了什么.我想首先知道为什么如果没有调用复制构造函数会发生这种情况,并且如果在这种情况下有一种方法可以在我的复制构造函数中使用非const,也就是使用vector::emplace_back(),因为它似乎是这个问题仅使用vector::emplace_back().

Sto*_*ica 17

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

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

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

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

您可以通过将移动构造函数返回来轻松地使错误变得沉默,例如,通过显式默认它:

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

    Vertex(Vertex&&) = default;

    Vertex(Vertex&) 
    { 
        std::cout << "Calling copy constructor\n";
    }
};
Run Code Online (Sandbox Code Playgroud)

永远不要忘记,在C++ 11中,0/3的规则成为0/3/5的规则.仔细考虑为您的类移动语义.

  • @Zebrafish - 不,在重新分配时它试图明确地移动它们.默认情况下不复制.当它接受const引用时,重载决策可以将复制构造函数称为回退.但在你的情况下,根本没有可行的过载. (3认同)
  • 请记住,如果你使用带有`vector`的类,最好确保移动构造函数是`noexcept`,否则它会尝试使用复制构造函数,如果没有,那么你将失去对异常的保证矢量的安全性.(默认的移动构造函数通常应该隐式地成为`noexcept`.) (2认同)

M.M*_*M.M 13

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

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

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

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

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

在不太详细的情况下,非const复制构造函数和没有move-constructor的组合意味着该对象未能通过MoveInsertable要求,因为所述要求中的rvalue参数不会绑定到非const左值引用.