【问题标题】:How to add elements to vector without invoking copy-constructor?如何在不调用复制构造函数的情况下将元素添加到向量?
【发布时间】:2015-07-21 13:26:57
【问题描述】:

由于某种原因,我的临时本地对象在添加到向量时总是被复制构造/销毁,由于嵌套的std::reference_wrapper 导致问题,由于之后的复制构造和销毁(std::reference_wrapper 目标而变得无效)位于被破坏的对象内 -> 如果源对象被破坏,它们在复制构造的对象中无效)。但如果可能的话,我想完全避免额外的复制/销毁——这似乎是不可能的,因为无论我尝试什么,它总是想调用复制构造函数(即使使用std::vector::emplace_back)。

考虑到这个简单的例子(为了在不涉及std::reference_wrapper 的情况下更容易理解),它总是尝试调用复制构造函数 - 我不明白为什么。

#include <vector>

class A{
public:
  A(int a) : a(a){ }
  int getInt() const{ return a; }
  A(const A&) = delete; /* to deny copy-construction */
private:
  int a;
};

int main(int argc, char* argv[]){
  std::vector<A> vec;
  vec.emplace_back(3);              /* tries to call copy constructor */
  vec.push_back(A(3));              /* tries to call copy constructor */
  vec.push_back(std::move(A(3)));   /* tries to call copy constructor */
  return 0;
}

任何想法我在这里缺少什么?

【问题讨论】:

  • a) 前两次尝试:为什么不应该这样做? b)关于 std::move 部分,您缺少移动构造函数(即 r 值引用参数)。
  • 你用的是什么编译器?如果移动构造函数不是自动生成的,那么你永远不能希望调用移动构造函数。另外,对于 c++11,如果你想禁用复制,你应该考虑使用A(const A&amp;) = delete
  • 我正在使用 MS Visual Studio 2013。感谢A(const A&amp;) = delete 的建议。
  • 读取this emplace_back reference,存储的类型必须是move insertable,这表明可以像::new((void*)p) T(rv)一样使用placement new,这将调用移动构造函数如果没有移动构造函数,则为复制构造函数。
  • 听起来你的类包含自引用。对于这种情况,您必须编写自己的复制/移动构造函数以获得正确的语义。大多数vector 操作要求对象至少是可移动的。仅仅完全禁用复制/移动对您没有多大帮助。

标签: c++ c++11 vector copy-constructor move-semantics


【解决方案1】:

根据 Visual Studio 2013 documentation,强调我的:

“Rvalue references v3.0”添加了新规则,以在特定条件下自动生成移动构造函数和移动赋值运算符。但是,由于时间和资源限制,这在 Visual Studio 2013 的 Visual C++ 中并未实现

Visual Studio 2013 被指定为使用 Rvalue 引用 v2.1。

注意:正如 cmets 中的 T.C. 注释,在您的示例中显式禁用复制构造函数也存在问题。根据cppreference.com

如果没有为类类型(结构、类或联合)提供用户定义的移动构造函数,并且以下所有情况都为真:

  • 没有用户声明的复制构造函数
  • 没有用户声明的复制赋值运算符
  • 没有用户声明的移动赋值运算符
  • 没有用户声明的析构函数
  • (C++14 前)由于下一节中详述的条件,隐式声明的移动构造函数未定义为已删除

然后编译器将使用签名 T::T(T&&) 将移动构造函数声明为其类的内联公共成员。

一个类可以有多个移动构造函数,例如T::T(const T&&) 和 T::T(T&&)。如果存在一些用户定义的移动构造函数,用户仍然可以强制生成带有关键字 default 的隐式声明的移动构造函数。

这意味着您的示例代码还会阻止自动生成移动构造函数(即,它有一个用户声明的复制构造函数)。

您需要显式声明您的移动构造函数和/或移动赋值运算符。以下适用于您的示例。

class A
{
public:
    A(int a) : a(a) {}

    A(const A&) = delete;

    A(A&& other) : a(other.a) {}

    int getInt() const { return a; }

private:
    int a;
};

int main(int argc, char* argv[])
{
    std::vector<A> vec;
    vec.emplace_back(3);
    vec.push_back(A(3));
    vec.push_back(std::move(A(3)));

    return 0;
}

【讨论】:

  • VS2013 的特性在这种情况下无关紧要。该类具有用户声明的复制构造函数,因此即使在符合标准的编译器中,它也不会获得隐式声明的移动构造函数。
  • @T.C.您是正确的,明确声明复制操作会禁用移动操作的自动生成。我只是假设人为的示例只是显式禁用复制构造函数以验证代码仍然有效(即,依赖于移动操作)。
  • @T.C.假设这是一个列表而不是向量(因此内部缓冲区不会加倍),您的观点是否仍然成立?为什么不使用placement new?
  • @JamesAdkison 您的假设是正确的 - 我只是明确禁用了复制构造函数以进行验证
猜你喜欢
  • 1970-01-01
  • 2011-02-04
  • 2017-09-26
  • 2014-02-07
  • 1970-01-01
  • 2015-03-29
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
相关资源
最近更新 更多