1

Having a struct such as:

struct PAIR {
    vector<double> a;
    vector<double> b;
};

Is using a function like the following a proper way to release the memory after defining and populating such a struct? If not, how do you deal with this situation?

void release(PAIR& p){
   vector<double>().swap(p.a);
   vector<double>().swap(p.b);
}

Isn't there a way to call some predefined/std function on PAIR itself to release memory?

Note that I'm not using new, etc. so definitions are simply like PAIR p;. Also, the struct is much more complex than just a pair of vectors that could have been defined using a std::pair.

All the related questions in SO on releasing memory for vectors are either about vectors themselves or vectors of a struct, not a struct containing multiple vectors. I'm looking for an elegant way to release memory used by such a struct.

Context

The vectors get really big, and I want to release the memory as soon as I can. But that lifetime/usability reaches in the middle of function! I don't want to spread the functionality in this function to multiple functions. These are pretty complicated computations and don't want to mess things up.

NULL
  • 759
  • 9
  • 18
  • 1
    So you want to keep a struct alive (valid) but just change all vector capacities to 0? Is that what you mean by releasing the memory? – WindyFields Aug 20 '17 at 15:06
  • You'll have to give a bit more detail on what you mean by "release the memory". When do you want to do this, and what should the result be? – Pete Becker Aug 20 '17 at 15:08
  • Your swapping trick is what is recommend to release memory of a vector. You also may try to call `clear`and C++11 `shrink_to_fit` but alas there is no guarantee that `shrink` will effectively release something (as the doc says). – Jean-Baptiste Yunès Aug 20 '17 at 15:08
  • @WindyFields not really. I rather have it totally released, but the problem is I don't want to refactor a ton of code for definitions be something like `new PAIR()` – NULL Aug 20 '17 at 15:09
  • 3
    You don't need to do anything to release memory. When your `PAIR` object's lifetime ends, all resources are automatically released. – Kerrek SB Aug 20 '17 at 15:09
  • 2
    @KerrekSB OP wants to resize its vector to 0 and release all memory used as if it allocated a new one; all of this during the lifetime of its vector. – Jean-Baptiste Yunès Aug 20 '17 at 15:10
  • @Jean-BaptisteYunès then, still, simply end the lifetime of the object. – Marcus Müller Aug 20 '17 at 15:12
  • 1
    @MarcusMüller suppose the pair is a received parameter (by ref)... How would you solve the problem? – Jean-Baptiste Yunès Aug 20 '17 at 15:15
  • OP should be more clear regarding the constraints on the containing object. There is no significance to the fact that is multiple vectors or one. The question is if OP is allowed to kill `PAIR`. – Rotem Aug 20 '17 at 15:15
  • Do you particularly want to keep using the memory used directly by the PAIR struct? – Mic Aug 20 '17 at 15:18
  • @Mic not really, dividing struct to multiple structs is much easier than shredding the function. – NULL Aug 20 '17 at 15:19
  • 1
    What's wrong with the good old `p = Pair{};`? – juanchopanza Aug 20 '17 at 18:22
  • @juanchopanza oh shoot you're really making me feel terribly stupid. haha – NULL Aug 20 '17 at 21:04

3 Answers3

3

Given function does not release memory on the stack actually. It is approximately equivalent to

p.a.clear(); 
p.a.shrink_to_fit();

The vector itself remains in the memory (just with 0 elements).

Remember, any memory that was allocated on the stack (~ without the use of new) gets released only when the variable occupying this memory goes out of scope, not earlier.

So if you have a variable on the stack and want to delete it, you can just limit its scope:

struct PAIR {
    vector<double> a;
    vector<double> b;
};

int main()
{
  // some stuff before...
  {
     PAIR p;
     // some stuff with p object...
  } // here p gets deleted (all memory gets released)
  // some stuff after...
}

You mentioned new PAIR. With pointers it would look like this:

int main()
{
   // some stuff before...
   PAIR* p = new PAIR;
   // some stuff with p object...
   delete p; // here p gets deleted (all memory gets released)
   // some stuff after...
}

Or as commentators requested:

int main()
{
   // some stuff before...
   {
     auto p = std::make_unique<PAIR>();
     // some stuff with p...
   } // here p gets deleted (all memory gets released)
   // some stuff after...
}

Is that what you wanted to achieve?

WindyFields
  • 2,697
  • 1
  • 18
  • 21
  • How is a `swap` equivalent to `clear`+`shrink_to_fit`? – Rotem Aug 20 '17 at 15:17
  • 1
    Neither guarantee memory is released. Both are implementation dependent. The only way to be sure is invoke its destructor I guess. – Robinson Aug 20 '17 at 15:18
  • 2
    Perhaps I'm being dense, could you explain how an implementation of `swap` works in which the memory is not freed? The memory is now owned by the temporary `vector` which ends its life after the call. – Rotem Aug 20 '17 at 15:21
  • 1
    Although `shrink_to_fit` is technically non-binding, I think it's safe to count on standard library authors not purposely screwing over their users. So if `shrink_to_fit` doesn't reduce the memory usage, it's because doing so would have no benefits (perhaps because the released memory would be too small to be reused by the allocator). If that's not the case, then file a bug report with your vendor. – Benjamin Lindley Aug 20 '17 at 15:24
  • @BenjaminLindley great that you commented here, I wanted to ping you on this! So do you recommend this way instead of swap? I was following your recommendation here: https://stackoverflow.com/questions/10464992/c-delete-vector-objects-free-memory – NULL Aug 20 '17 at 15:28
  • @NULL, I am afraid that I am just trying to solve a different problem than you have... But you mentioned somewhere in comments that `new PAIR()` would solve the issue but you don't want to refactor that is why I gave this answer – WindyFields Aug 20 '17 at 15:31
  • @WindyFields The memory on the stack occupied by std::vector is negligible and constant. The dynamic array in which the elements are stored is the relevant issue. – Rotem Aug 20 '17 at 15:32
  • @WindyFields no that's perfectly fine. This is a good solution to me. I'm still thinking about `swap` and `shrink_to_fit`. I'm confused or perhaps surprised that neither provide a guarantee. – NULL Aug 20 '17 at 15:33
  • Question says "I'm looking for an elegant way to release memory used by such a struct." but the struct is on the stack not on the heap:) – WindyFields Aug 20 '17 at 15:34
  • @WindyFields None whatsoever, I was just challenging your claim that it does not release the memory. – Rotem Aug 20 '17 at 15:35
  • @WindyFields The elements are stored on the heap regardless of where the vector is defined. https://stackoverflow.com/questions/8036474/when-vectors-are-allocated-do-they-use-memory-on-the-heap-or-the-stack – Rotem Aug 20 '17 at 15:35
  • 2
    @NULL: Do I recommend what? Using `clear()` followed by `shrink_to_fit()`? It doesn't matter, IMO. Both methods work, despite disclaimers that the C++ standard does not guarantee them to work. – Benjamin Lindley Aug 20 '17 at 15:37
0

Does PAIR have to be a POD? Maybe something like this might work for you?

struct PAIR
{
private:
    std::unique_ptr<std::vector<double>> aptr;
    std::unique_ptr<std::vector<double>> bptr;
    PAIR(const PAIR&) = delete;
public:
    PAIR() : aptr(std::make_unique<std::vector<double>()), 
             bptr(std::make_unique<std::vector<double>()) {}
    ~PAIR() { release(); }
    std::vector<double> &a = *aptr;
    std::vector<double> &b = *bptr;
    void release()
    {
        aptr.reset();
        bptr.reset();
    }
...
};
Mic
  • 331
  • 1
  • 4
  • Why not `std::unique_ptr` with [`std::unique_ptr::reset`](http://en.cppreference.com/w/cpp/memory/unique_ptr/reset)? – Zereges Aug 20 '17 at 16:25
  • Apologies, I was just sketching a rough idea. TBH, I'm inclined to think swap is the way to go. Even if swap doesn't explicitly guarantee that the underlying storage is swapped, I can't think of any logical way to implement swap that won't result in the storage being released when the temporaries are destroyed. – Mic Aug 20 '17 at 16:33
  • @Zereges. Even it's kinda hackish, decided to tidy it up anyway. Thanks. – Mic Aug 20 '17 at 16:54
  • The point was that copy-constructors/assignment now don't leak (or rather don't double free) since they are implicitely deleted (so you don't need to delete it yourself) and move now works. Also you don't need destructor at all. – Zereges Aug 20 '17 at 16:57
  • Yep. I was thinking in terms of using placement new and reinterpret_cast but changed it to use a pointer to look a bit simpler. It's probably no harm to leave them there, if someone was really going to try this method I imagine they'd do the same and use placement new et al. – Mic Aug 20 '17 at 18:13
-1

simply .resize(0) the vectors.

Marcus Müller
  • 34,677
  • 4
  • 53
  • 94
  • 1
    `Calling resize() with a smaller size has no effect on the capacity of a vector. It will not free memory.` from here: https://stackoverflow.com/questions/1155693/stdvector-resize-downward – NULL Aug 20 '17 at 15:00
  • 1
    `resize(0)` only changes the size of the vector to 0, won't release the memory occupied. – songyuanyao Aug 20 '17 at 15:00
  • I disagree: there's no *guarantee* `resize` releases the memory, but it *might*. There's also no guarantee that `std::vector v()` takes doesn't allocate memory, so the swap-trick is an old, and not very reliable, trick, and nothing more. – Marcus Müller Aug 20 '17 at 15:05
  • I think std::vector::clear followed by std::vector::shrink_to_fit is the way to do it. However std::swap is more terse, though I'm not sure it's more understandable from a maintenance perspective (and as Marcus points out, it's a "trick" dependent upon implementation). – Robinson Aug 20 '17 at 15:08
  • 2
    @robinson `shrink-to_fit` does not provide that guarantee too. – Jean-Baptiste Yunès Aug 20 '17 at 15:11
  • 3
    @MarcusMüller "but it might." No, [resize](http://en.cppreference.com/w/cpp/container/vector/resize#Notes) is not allowed to do this. "Vector capacity is never reduced when resizing to smaller size because that would invalidate all iterators, rather than only the ones that would be invalidated by the equivalent sequence of pop_back() calls." – songyuanyao Aug 20 '17 at 15:12
  • @songyuanyao hm, that is contradicting to my understanding, since afaik the contract is that `resize` calls `erase`, and that might very well release memory – Marcus Müller Aug 20 '17 at 15:13
  • I recommend you guys to give an updated answer to releasing memory used by vectors in this question: https://stackoverflow.com/questions/10464992/c-delete-vector-objects-free-memory – NULL Aug 20 '17 at 15:22
  • It's great and very helpful to know that all of these don't provide a guarantee to release memory. – NULL Aug 20 '17 at 15:25
  • 1
    @NULL The guarantee with swap is that vector will be in its default constructed state. That is implementation specific. In Visual Studio 2017, that is a 16 byte allocation (64 bit build) or 8 byte allocation (32 bit build). At least that's what I saw when I stepped into the default constructor. – Robinson Aug 20 '17 at 16:45
  • @Robinson awesome! Now this is an understandable answer about the guarantees – NULL Aug 20 '17 at 16:52
  • @NULL I stupidly gave the example in the debug version. The release version does not perform the allocation. – Robinson Aug 20 '17 at 17:08
  • Surely the swap case indirectly guarantees releasing the memory? Guarantees that are made by swap mean that a reasonable implementation has to end up with the temporary owning the memory originally owned by the original vector, which will then be released when the temporary goes out of scope. – Mic Aug 20 '17 at 19:09