How do I free memory a vector has used up?

I'm not sure how to free memory a vector used up. For example I have a vector like this:

 
std::vector<Texture*> textures;


When I try deleting them since I used new and then using swap:

1
2
3
4
for (size_t i = 0; i < textures.size(); i += 1) {
   delete textures[i];
}
std::vector<Texture*>().swap(textures);


I indeed get 0 elements after calling .size(). However, when I look at the Process Memory report in Visual Studio, the memory usage rises until I've reached the total amount of possible "textures" in my app.

But since I'm changing scenes in my game, I'd love to clear the memory in that scene and start fresh on the next.

It seems like once an N size vector is created, the memory is allocated for all those elements and never released. What would you all recommend?

I'll share a sample of the code below just in case it helps. Thanks!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <vector>

struct Texture {
	unsigned int buffer = 0;
};

class Assets {
public:

	void LoadTexture() {
		textures.push_back(new Texture());
	}

	void Cleanup() {
		for (size_t i = 0; i < textures.size(); i += 1) {
			delete textures[i];
		}

		std::vector<Texture*>().swap(textures);
	}

	std::vector<Texture*> textures;
};


int main() {
	Assets assets;
	assets.LoadTexture();
	std::cout << assets.textures.size() << std::endl;

	assets.Cleanup();
	std::cout << assets.textures.size() << std::endl;
}
> I'll share a sample of the code below just in case it helps
given that your code does not show your issue, no, it doesn't help


¿why do you need dynamic allocation for the `Texture'?
¿if it is actually necessary, can't use a smart pointer?
Hello Ne, I just wanted to learn how to do it without smart pointers as a personal project. It's not for a client or anything.

And yes, the code has no issues. I guess my question is, if I create a vector of let's say 10 textures. That memory allocated, is that freed when I call std::vector<Texture*>().swap(textures);??

Visually I don't see my memory reduce on visual studio. It definitely stops increasing once I've allocated enough memory for all my textures.
That memory allocated, is that freed when I call std::vector<Texture*>().swap(textures);??
Why not using textures.clear() instead of that confusing swap()?

Visually I don't see my memory reduce on visual studio.
That could be caching. The system doesn't return the memory immediately to the pool unless it is really necessary so that it can be faster reused by your program.
The memory is still allocated to the program by the OS. That's (probably) what visual studio is showing you: memory allocated by the OS to the process. But within the process, that memory is available for the next time new is called, either by you or a library.

In other news, if the vector "owns" the memory then you are you using a vector of pointers at all? Doing so invites memory leaks. Why not vector<Texture> directly?
http://www.cplusplus.com/reference/vector/vector/clear/ writes
Clear content
Removes all elements from the vector (which are destroyed), leaving the container with a size of 0.

A reallocation is not guaranteed to happen, and the vector capacity is not guaranteed to change due to calling this function. A typical alternative that forces a reallocation is to use swap:
vector<T>().swap(x); // clear x reallocating


http://www.cplusplus.com/reference/vector/vector/shrink_to_fit/ writes:
Shrink to fit
Requests the container to reduce its capacity to fit its size.

The request is non-binding, and the container implementation is free to optimize otherwise and leave the vector with a capacity greater than its size.


These imply that
ebz wrote:
I indeed get 0 elements after calling .size()

is mostly irrelevant. The simple textures.clear() does achieve the size() == 0. For allocated memory one should look at textures.capacity().

The swap idiom does guarantee "reallocation":
1
2
3
4
5
6
7
8
9
vector x;
// fill x

{
  vector y; // empty
  swap(x, y);
  // x is now empty and y has capacity
} // deallocate y that owns x's former capacity
// x is truly empty 


However, a vector of pointers has allocated memory only for pointers. Very small objects. That vector in itself consumes insignificant amount of memory (unless it has humongous amount of elements).


Even with vector<Texture> (which is most likely much smarter than use of smart pointers that in turn trump raw pointers) what do we know about struct Texture?

The "test" program has Texture that is nothing more than one int. A pointer to Texture is at least as large as the Texture itself.
The test program creates only one Texture object.
The vector has at most one pointer to that one Texture object.
The vector object has a pointer to array, size, capacity, and whatnot in them. They are there whether the vector is empty or not.

The test program allocates (and deallocates) memory for one int and one pointer from free store.


Or put other way, it is difficult to see whether anything did happen, when almost nothing did happen.
Why use manual memory management, having a std::vector of pointers? Let the vector handle the memory for you. The vector allocates its memory on the heap, you don't need to bother manually.

If creating a Texture object is "expensive" in time or resources you can use std::vector's emplace() or emplace_back() member functions, so no temporary copies are made to be tossed around when adding elements to your vector.

Not using a vector of pointers removes the drudgery -- and possible memory leaks/errors -- of having to manually manage heap memory.

Let std::vector handle the minutiae of dealing with memory.

If you need the address of an element of a vector's element use the addressof operator&.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <vector>

struct Texture
{
   unsigned int buffer { };
   unsigned int data   { };
};

struct Assets
{
   void LoadTexture()
   {
      for (size_t itr { }; itr < 25; itr++)
      {
         textures.emplace_back(Texture());  // create in-place
      }
   }

   void Cleanup()
   {
      /*	for (size_t i = 0; i < textures.size(); i += 1)
         {
            delete textures[i];
         }

         std::vector<Texture*>().swap(textures); */

      textures.clear();  // no manual memory management, let the vector handle the drudgery
   }

   std::vector<Texture> textures;
};

int main()
{
   Assets assets;
   assets.LoadTexture();

   std::cout << "size: " << assets.textures.size() << ", capacity: " << assets.textures.capacity() << '\n';

   std::cout << "\nAddress of 5th element: " << "0x" << std::hex << &assets.textures[4] << std::dec << "\n\n";
   assets.Cleanup();

   std::cout << "size: " << assets.textures.size() << ", capacity: " << assets.textures.capacity() << '\n';
}
size: 25, capacity: 28

Address of 5th element: 0x00CEE380

size: 0, capacity: 28

The vector still has the memory allocated after clear(), but it is unusable since you cleared the elements. They are destroyed properly.

If you are constantly adding to a vector and removing the elements in a block repeatedly you want to retain ACCESS to the allocated memory even if it is not usable. Memory allocation on the heap is not cheap. Reusing the same memory block again and again is a performance boost over having to reallocate memory each time.
If you really want to have a vector of pointers, then at least use std::unique_ptr so that allocation/free is done automatically. Consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include <vector>
#include <memory>

struct Texture {
	unsigned int buffer { };
	unsigned int data { };
	Texture(int d) : data(d) {}
};

using Ttext = std::unique_ptr<Texture>;

class Assets {
public:

	void LoadTexture() {
		for (int itr { }; itr < 25; ++itr)
			textures.push_back(std::make_unique<Texture>(itr));
	}

	void Cleanup() {
		textures.clear();
	}

	void ShowTexture() {
		for (const auto& t : textures)
			std::cout << t->data << "  ";

		std::cout << '\n';
	}

	std::vector<Ttext> textures;
};


int main()
{
	Assets assets;

	assets.LoadTexture();

	std::cout << "size: " << assets.textures.size() << ", capacity: " << assets.textures.capacity() << '\n';
	std::cout << "\n5th element is: " << assets.textures[4]->data << "\n";
	std::cout << "\nAddress of 5th element: " << "0x" << std::hex << assets.textures[4].get() << std::dec << "\n\n";

	assets.ShowTexture();
	assets.Cleanup();

	std::cout << "\nsize: " << assets.textures.size() << ", capacity: " << assets.textures.capacity() << '\n';
}

The OP said they wanted to learn how to use a vector of pointers without using smart pointers.

http://www.cplusplus.com/forum/beginner/273382/#msg1179114

A conscious design choice. Not one I'd make, I know too well the pitfalls manual memory management can have.

Great learning experience, though, IMO.
Consider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <string_view>
#include <vector>
#include <iostream>

class B
{
public:
  virtual ~B() = default;
  virtual std::string_view name() const = 0;  
};

struct D1 : public B { std::string_view name() const override { return "D1"; } };
struct D2 : public B { std::string_view name() const override { return "D2"; } };

class polymorphic_B
{
  B* b_ = nullptr;
  
public:
  template <typename T> 
    polymorphic_B(T&& x): b_(new T{std::forward<T>(x)}) {} 

  ~polymorphic_B() { delete b_; }
  
  polymorphic_B(polymorphic_B const &) = delete;  
  polymorphic_B& operator=(polymorphic_B const&) = delete;  
  
  polymorphic_B(polymorphic_B&& other) { *this = std::move(other); }  
  polymorphic_B& operator=(polymorphic_B&& rhs) 
  { 
    b_ = std::exchange(rhs.b_, nullptr);
    return *this; 
  }  
  
  std::string_view name() const { return b_->name(); }
};

int main()
{
  std::vector<polymorphic_B> xs;   
  
  xs.push_back(D1{});
  xs.push_back(D2{});
  
  for (auto const& x: xs) std::cout << x.name() << '\n';
}
Last edited on
Thanks you all for your great replies. It really helped a lot!
Topic archived. No new replies allowed.