Bruno Grieder
Bruno Grieder

Reputation: 29824

"moving" from the stack to the heap?

I need to write a C wrapper around a C++ lib and I need objects allocated on the heap. Functions and methods of the C++ lib use and return objects allocated on the stack.

I know, I can "transfer" an object from the stack to the heap via copy i.e. auto heapObj = new Foo(stackObj); but would like to avoid copy and try to move instead if I can.

This seems to "work" (to my surprise). Is there a copy happening behind the scenes ? If not, is this pattern safe to use ?

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;
}

Upvotes: 2

Views: 2042

Answers (2)

Max Langhof
Max Langhof

Reputation: 23691

First of all, moving an int or a pointer is equivalent to a copy. That is, if you had a

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

then moving it is not going to be cheaper than copying it (ignoring ownership of data for now). Coincidentally, the above is basically what std::vector looks like from far away: A size and capacity member plus some pointer to a chunk of memory.

The important thing about moving vs copying is what happens in regards to ownership of resources. std::vector has ownership of some heap memory (data). If you copy a std::vector, that heap memory must be copied, so that both the original and the copy can have ownership of their own data. But if you move it, then only the moved-to vector needs to retain ownership, so the data pointer can be handed from one to the other (instead of all the data), because the ownership can be "stolen" from the moved-from object.

This is why there is no conflict in "moving" your object from the stack to the heap: The object itself is still basically copied from one place to the other, but the resources it (or its subobjects, like big) owns are not copied but moved ("stolen").

Upvotes: 4

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385194

Any time a move "actually happens", it's because there is some indirect resource within the moved thing. Some resource that's referred to by a handle that can be cheaply swapped (a pointer copied over, for example; the pointee remains where it was). This is generally accomplished via pointers to dynamically-allocated things, such as the data stored within a vector.

The stuff you're trying to move is a vector. As such, it is already dynamically-allocated and the move is easy. It doesn't really matter where the actual std::vector object lives, nor where the Foo lives — if there's an indirect resource, a move is probably possible.

In other cases, a move constructor or move assignment will actually just trigger a copy of whatever data is inside. When everything (recursively) in the "thing" has automatic storage duration, you can pretty much guarantee that a copy will be required. But that's not the case here.

Upvotes: 1

Related Questions