1

A third-party library has the API Huge computeHuge(). It returns the object itself, not a reference/pointer. I have no control over the object or the API.

I've got two classes:

class Bar {
  Huge h;
  Bar(Huge &huge): h(huge);
}

class Foo {
  Bar b;

  Foo() {
    Huge h = computeHuge();
    b = Bar(h);
  }

Unfortunately, this design results (temporarily) in two copies of a huge object: one copy exists in the Foo constructor, and another exists inside the Bar object. Once the Foo constructor exits, there is only one copy, but I need double the memory inside the constructor. Since h could be hundreds of GB, this matters.

One solution to this problem is to make Foo the owner of h:

class Bar {
  Huge &h;
  Bar(Huge &huge): h(huge);
}

class Foo {
  Bar b;
  Huge h;

  Foo() {
    h = computeHuge();
    b = Bar(h);
  }

This does successfully eliminate having two copies of h, but it doesn't really make sense in my application: Baris the Right Thing to hold h. How can I:

  1. Call computeHuge() in the Foo constructor
  2. Let Bar retain ownership of h
  3. All without ever needing two copies of h in memory?
crockeea
  • 21,651
  • 10
  • 48
  • 101
  • 1
    I'm not convinced that you could have hundreds of GB on the stack. Usually, the default stack size is 1 MB. I think `h` internal data is dynamically allocated (assuming you have a RAM memory big enough to store hundreds of GB, which I seriously doubt). But if you have access to `Huge`'s internal data, you could perhaps hold a pointer to it instead. – Fareanor Feb 27 '20 at 22:15
  • True. `Huge` is a wrapper around a very large vector, and this vector is presumably allocated on the heap. But I don't have any control over it because the `Huge` object in `Foo()` will be cleaned up when the constructor exits, which will delete the vector. Even if I could access the internal vector, I coudln't keep it from being deleted when the constructor exits. I am using a *very* large EC2 instance, so plenty of memory :) – crockeea Feb 27 '20 at 22:16
  • As advised in the given answer, you can move your `std::vector` (If you can't move `Huge` directly, which would be preferred). It should do the trick :) – Fareanor Feb 27 '20 at 22:18
  • ^ if not that: Make Bar generate the (empty) Huge object, then have an get-huge-reference function in Bar, then use that to fill out the object in Foo? Like a get-image-buffer type function in image libraries? – Dave S Feb 27 '20 at 22:19
  • @DaveS Can you write that up as an answer? – crockeea Feb 27 '20 at 22:24
  • You must have a default constructor for `Bar` somewhere right? When you create `Foo`, `Bar b` must be constructed. Does this in turn create a dummy `Huge h` that is taking a lot of memory? You can initialize `b` in the initialization list of `Foo`? – JohnFilleau Feb 28 '20 at 00:23
  • Sorry, I misread a key part of the question so my answer won't help you. – Dave S Feb 28 '20 at 00:58

1 Answers1

2

If Huge is moveable, this does not make any copies:

class Bar {
  Huge h;
  Bar(Huge huge): h(std::move(huge)) {}   // move huge into its final place, h
};

class Foo {
  Bar b;

  Foo() {
    Huge h = computeHuge();
    b = Bar(std::move(h));   // move h into constructor argument
    // h cannot be used here anymore
  }
};

For debugging purposes, this is a (tiny) Huge which can not be copied, but only moved. Every attempt to copy is a compiler error:

struct Huge {
    Huge() = default;
    Huge(Huge&& other) { std::cout << "move "; }
    Huge(const Huge& other) = delete;
    Huge& operator=(Huge&& other) { std::cout << "move= "; return *this; }
    Huge& operator=(const Huge& other) = delete;
};
alain
  • 11,939
  • 2
  • 31
  • 51
  • I tried your suggestion, but I'm getting the same behavior as before: double memory usage. Perhaps this means that my object isn't moveable? – crockeea Feb 27 '20 at 22:46
  • Actually, I'm just reading [this](https://stackoverflow.com/questions/14323093/are-there-any-use-cases-for-a-class-which-is-copyable-but-not-movable), and it turns out a non-moveable type is very rare and in some cases can't even be returned from a function, so `Huge` is probably moveable. Unless it has explicitly been made non-moveable, which would not make sense. I played with non-moveable [here](http://coliru.stacked-crooked.com/a/a5a615a1b9a21228). I think there is a still a copy being made somewhere else.. because as you can see in the previous link, no copy is made with the code here. – alain Feb 27 '20 at 23:27
  • @crockeea Can you change `Huge`? If yes I would `=delete` the copy-constructor and copy-operator=, then every copy is a compile error. – alain Feb 27 '20 at 23:38
  • Nope, it's in an external lib. – crockeea Feb 28 '20 at 00:08
  • Ok, then I would make a fake `Huge` (updated answer) to see where the copy is made. – alain Feb 28 '20 at 00:18
  • Found the missing `move`. Thanks! – crockeea Feb 28 '20 at 17:57