【问题标题】:"moving" from the stack to the heap?从堆栈“移动”到堆?
【发布时间】:2019-02-05 15:49:06
【问题描述】:

我需要围绕 C++ 库编写一个 C 包装器,并且我需要在堆上分配对象。 C++ lib 的函数和方法使用并返回分配在堆栈上的对象。

我知道,我可以通过复制将一个对象从堆栈“传输”到堆,即auto heapObj = new Foo(stackObj);,但我想避免复制,如果可以的话,尝试使用move

这似乎“有效”(令我惊讶)。幕后有副本吗?如果不是,这种模式可以安全使用吗?

main.h

class Foo {
   public:
    std::vector<int> big;

    explicit Foo(size_t len);

    Foo(Foo&& other) noexcept;

    // remove copy constructor
    Foo(const Foo &) = delete;

    // delete assignment operator
    Foo &operator=(const Foo &) = delete;


    size_t size();
};

main.cpp

#include <iostream>
#include "main.h"

Foo::Foo(size_t len) : big(len) {}

Foo::Foo(Foo&& other) noexcept : big(std::move(other.big)) {}

size_t Foo::size() { return this->big.size(); }

int main() {
    Foo ms(1000);  // on the stack
    ms.big[0] = 42;

    auto mh = new Foo(std::move(ms));  // on the heap (no copy?)

    std::cout << mh->size() << ", " << mh->big[0] << std::endl;

    delete mh;
}

【问题讨论】:

  • 如果您 = delete 复制构造函数和复制赋值运算符,您不必担心会生成秘密副本。如果发生这种情况,编译器会报错。
  • 如果您提供移动构造函数,您可能需要提供移动赋值运算符。这是3/5/0 规则的一部分。虽然在这种情况下你可以只使用编译器提供的。
  • big 中的数据一开始就不会在堆栈中。当然,堆栈上的部分会被复制。我不太确定这里有什么不清楚的地方。
  • @BrunoGrieder 如果Foo 没有被复制,那么它也不是成员。
  • @FedericoklezCulloca 也许对于某些类型,例如std::array,但对于大多数容器,它会复制该容器的内部状态(例如指向数据和大小的指针)并将原始容器清零。实际数据保留在原处。编辑:即使对于像std::array 这样难以移动的容器,它也会移动每个元素,因此例如移动std::vectorstd::array 仍然不仅仅是复制/清除。

标签: c++ c++17


【解决方案1】:

首先,移动int 或指针相当于复制。也就是说,如果你有一个

struct X {
  int a, b;
  int* data;
};

然后移动它不会比复制它便宜(暂时忽略data 的所有权)。巧合的是,上面基本上就是std::vector 从远处看的样子:一个sizecapacity 成员加上一些指向一块内存的指针。

移动与复制的重要之处在于资源所有权std::vector 拥有一些堆内存 (data)。如果您复制 std::vector,则必须复制该堆内存,以便原始和副本都可以拥有自己的数据的所有权。但是如果你移动它,那么只有移动到的向量需要保留所有权,所以data指针可以从一个传递到另一个(而不是所有数据),因为所有权可以从被移动的对象中“偷走”。

这就是为什么将对象从堆栈“移动”到堆中没有冲突的原因:对象本身基本上仍然从一个地方复制到另一个地方,但是它的资源(或其子对象,如big ) 拥有的不是复制而是移动(“被盗”)。

【讨论】:

  • Txs。在帖子开头将其设为小型 TLDR
  • @BrunoGrieder 我删除了它。问题中没有答案:)
  • @LightnessRacesinOrbit 确定;问题中的前提是错误的;我只是想避免对下一位读者进行不必要的阅读
  • @BrunoGrieder 不愿阅读的读者不是读者,可以放心忽略:P
  • @BrunoGrieder 在阅读问题时我希望它有错误的代码、错误的前提等,如果最重要的是它包含回答的尝试,我会完全糊涂;)
【解决方案2】:

任何时候“实际发生”的移动,都是因为移动的东西中有一些间接资源。可以廉价交换的句柄引用的某些资源(例如,复制的指针;指针保持在原来的位置)。这通常是通过指向动态分配的事物的指针来完成的,例如存储在向量中的数据。

你试图移动的东西是一个向量。因此,它已经是动态分配的,移动很容易。实际的 std::vector 对象在哪里,Foo 在哪里都无关紧要——如果有间接资源,移动可能是可能的。

在其他情况下,移动构造函数或移动赋值实际上只会触发内部数据的副本。当“事物”中的所有内容(递归)具有自动存储持续时间时,您几乎可以保证需要一个副本。但这里不是这样。

【讨论】:

    猜你喜欢
    • 2011-11-26
    • 2021-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-25
    • 2012-04-02
    • 2016-08-06
    • 2019-03-08
    相关资源
    最近更新 更多