【问题标题】:What is this strange copy constructor error complaining about?这个奇怪的复制构造函数错误在抱怨什么?
【发布时间】:2018-03-26 09:58:08
【问题描述】:

我正在使用 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&amp;,则它编译得很好。

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

错误:'Vertex&' 类型的非常量引用的初始化无效 来自“顶点”类型的右值

我认为复制构造函数不必是 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()才会出现这个问题。

【问题讨论】:

  • 但是如果通过向量重新分配空间,则必须调用复制/移动构造函数...我想说这里发生的所有事情(可能除了 MS 接受的非 const 复制构造函数)有其合理的理由......
  • @在必须重新分配向量的情况下,将使用位于旧容器中的左值调用复制构造函数。
  • 请记住,如果复制构造函数可以更改由左值引用引用的旧对象,那将是非常不可取的
  • 请注意,MS 标准库的维护者 Stephan Lavavej 曾多次表示/Za 开关测试不佳,标准库并不真正支持。
  • 另一件事是没有任何充分的理由拥有非常量复制构造函数。使用mutable 类成员可能是一个更好的解决方案,无论你有什么动机

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


【解决方案1】:

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

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

【讨论】:

  • 那么我的非常量复制构造函数何时使用 R 值引用调用?什么时候重新分配?当它重新分配时,它使用复制构造函数来移动所有旧容器元素,但那些旧容器元素是左值。 R值是多少?被安置回来的那个?它是否重新分配新内存,复制构建现有的内存,然后在新内存的最后一个位置构建新的重新安置的内存?
  • 哦,对不起,我明白了,它不会复制构造它们,而是移动构造它们,我错过了移动构造函数。奇怪,错误信息没有提到这一点。
  • @Zebrafish - 不,在重新分配时它会尝试显式移动它们。默认不复制。当重载解析接受 const 引用时,它可以调用复制构造函数作为后备。但在你的情况下,根本没有可行的过载。
  • @Zebrafish - MSVC 有一个实现错误。这很不幸,但最终不是您的代码有问题
  • 请记住,如果您使用带有vector 的类,最好确保移动构造函数为noexcept,否则它将尝试使用复制构造函数,如果是这样的话不可用,那么您将失去对向量异常安全性的保证。 (默认的移动构造函数通常应该隐式地最终成为noexcept。)
【解决方案2】:

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

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

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

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

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

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

【讨论】:

  • 重新分配时的右值究竟是什么?所有旧的容器元素都是 l 值,最后一个新元素的 emplace_back 参数被发送到普通的构造函数,不是吗?
  • @Zebrafish 请参阅我链接的 MoveInsertable 的描述。右值是为了指定 MoveInsertable 要求而假设的:包含的类型必须可以从右值构造。
  • 您可能会发现一个典型的向量实现相当于 std::move 在旧元素上移动到新位置(使用一些额外的 hokum 来处理非 noexcept 移动构造函数)跨度>
猜你喜欢
  • 2020-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-15
  • 1970-01-01
  • 2011-06-24
  • 1970-01-01
相关资源
最近更新 更多