Jump to content
Site Stability Read more... ×
  • Advertisement
Sign in to follow this  
Maxjen

C++ c

This topic is 2033 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi,

 

I made a very simple container class because I need a vector where I can remove elements in the middle without changing the position of the other elements but I am worried that it doesn't free up everything when I delete it. Here is the code:

template <typename T>
class Vector
{
private:
	T* elements;
	int elementCount;
	int elementCapacity;
	int freeElement; // first free element
	int* freeElementList; // each array element is the index of the next free element
public:
	Vector(int initialCapacity) {
		elementCount = 0;
		elementCapacity = initialCapacity;
		elements = (T*)malloc(elementCapacity * sizeof(T));

		freeElement = 0;
		freeElementList = (int*)malloc(elementCapacity * sizeof(int));
		for (int i = 0; i < elementCapacity; ++i)
			freeElementList[i] = i + 1;
	}

	~Vector() {
		free(freeElementList);
		free(elements);
	}

	int addElement(T& element) {
		if (elementCount == elementCapacity) {
			elementCapacity *= 2;

			int* oldFreeElementList = freeElementList;
			freeElementList = (int*)malloc(elementCapacity * sizeof(int));
			memcpy(freeElementList, oldFreeElementList, elementCount * sizeof(int));
			free(oldFreeElementList);
			for (int i = elementCount; i < elementCapacity; ++i)
				freeElementList[i] = i + 1;

			T* oldElements = elements;
			elements = (T*)malloc(elementCapacity * sizeof(T));
			memcpy(elements, oldElements, elementCount * sizeof(T));
			free(oldElements);
		}
		elements[freeElement] = element;

		int result = freeElement;
		freeElement = freeElementList[freeElement];
		elementCount++;

		return result;
	}

	void removeElement(int index) {
		freeElementList[index] = freeElement;
		freeElement = index;
		elementCount--;
	}

	T getElement(int index) {
		return elements[index];
	}

};

For example if I use it to store STL vectors will the destructor actually delete all their elements?

 

EDIT: sorry, I forgot to set the right title.sad.png It was supposed to be something like "C++ custom container"

Edited by Maxjen

Share this post


Link to post
Share on other sites
Advertisement

Ok, I changed the class to use new/delete instead of malloc/free and I am calling the destructors manually. However, I don't know if I still have to call the constructors manually. I think right now the line "elements[freeElement] = element;" uses the copy constructor to construct the elements, or not?

template <typename T>
class Vector
{
private:
	T* elements;
	int elementCount;
	int elementCapacity;
	int freeElement; // first free element
	int* freeElementList; // each array element is the index of the next free element
	bool* isUsed;
public:
	Vector(int initialCapacity) {
		elementCount = 0;
		elementCapacity = initialCapacity;
		elements = new T [elementCapacity];

		freeElement = 0;
		freeElementList = new int [elementCapacity];
		isUsed = new bool [elementCapacity];
		for (int i = 0; i < elementCapacity; ++i) {
			freeElementList[i] = i + 1;
			isUsed[i] = false;
		}
	}

	~Vector() {
		for (int i = 0; i < elementCapacity; ++i) {
			if (isUsed[i])
				elements[i].~T();
		}

		delete [] freeElementList;
		delete [] elements;
		delete [] isUsed;
	}

	int addElement(T& element) {
		if (elementCount == elementCapacity) {
			elementCapacity *= 2;

			int* oldFreeElementList = freeElementList;
			freeElementList = new int [elementCapacity];
			memcpy(freeElementList, oldFreeElementList, elementCount * sizeof(int));
			delete [] oldFreeElementList;

			bool* oldIsUsed = isUsed;
			isUsed = new bool [elementCapacity];
			memcpy(isUsed, oldIsUsed, elementCount * sizeof(bool));
			delete [] oldIsUsed;

			for (int i = elementCount; i < elementCapacity; ++i) {
				freeElementList[i] = i + 1;
				isUsed[i] = false;
			}

			T* oldElements = elements;
			elements = new T [elementCapacity];
			memcpy(elements, oldElements, elementCount * sizeof(T));
			delete [] oldElements;
		}
		elements[freeElement] = element;
		isUsed[freeElement] = true;

		int result = freeElement;
		freeElement = freeElementList[freeElement];
		elementCount++;

		return result;
	}

	void removeElement(int index) {
		elements[index].~T();
		freeElementList[index] = freeElement;
		freeElement = index;
		elementCount--;
	}

	T getElement(int index) {
		return elements[index];
	}

};

About the rule of three: If I don't plan to copy instances of the class do I still need it?

Share this post


Link to post
Share on other sites

Even better would be to use smart pointers. wink.png

 

C++11 already contains an unique_ptr<> class in the standard library. When you use this unique pointer, it will automatically delete the object it's pointing too. This way, you don't need to worry very much like this about deallocating etc. The overhead is also usually insignificant. An example:

#include <memory>
#include "your_super_awesome_header.hpp"

int main()
{
    {
        auto smart_pointer = std::unique_ptr<your_super_awesome_class>(new your_super_awesome_class());
    } // The allocated object gets deleted here and its destructor will be called automatically.
}

Also, you should try to use initalization lists where possible.

 

Besides that, maybe it's a better idea to pass a pointer to Vector::addElement() so that you avoid copying overhead by just taking ownership, or create a function for rvalue references too.  Why are you actually creating a custom vector class? If you would use the standard one you would not have to deal with things like this.
I assume you are keeping pointers to the elements of your container, so you need them to be on a fixed position so you won't have invalid pointers? If that's the case, you could just save the pointer type, or even better, a smart pointer (unique or shared). And if this isn't an option for some reason, you could always use a std::deque or std::list, or another container like them. These keep the objects on the same place, but not all in a continous array like a vector does. This will involve minor overhead, but your vector class is written in a way which will use a lot of memory, which is in most cases a bigger problem.

 

Edit: replying to your latest post.

 

The rule of three is pretty (or very very) important. People expect your class to be able to copy itself without problems when a copy constructor exists. If they do that now, memory leaks will occur. In C++ this rule could also be replaced with the rule of five, you should look it up. Moving the vector would not have major overhead and could be used some time...

 

And the line "elements[freeElement] = element;" uses the assignment constructor, not the copy constructor. Usually they do the same for as far as I know.

 

You don't have to call the constructor manually. The object will be constructed one way or the other if you are creating it on the stack (except if you're writing some dirt code wink.png ). If you're allocating on the heap you're constructing a new object (and thereby calling it's constructor) by using new().

Edited by ProtectedMode

Share this post


Link to post
Share on other sites

How does getElement know that index is a valid item?  

How are you going to implement iterating through the entire vector?

index++ should point to the next valid item after index (index itself should also be guaranteed to be a valid item to begin with). index + 2 should be the second valid item after index, etc.

The container size should represent the number of valid items in the vector, and they should appear contiguous from outside the vector, even if internally they are not.

 

I guess the real question is, why do you need a vector that allows removal of elements without shifting the rest of the elements around?  The two largest benefits of vectors are random access to its items and truly contiguous arrangement of those items in memory.  IE, fast access to any item in the vector and fast, cache friendly access to all items in the vector.  You appear to be defeating both those benefits, to some degree, thus the question as to why you need a vector to begin with.

Share this post


Link to post
Share on other sites

I thought about it a bit more and I think I can actually do what I want with a map or a list. Initially it was important that the position of the elements remained the same because I uploaded the whole thing into an opengl vertex buffer. I wanted to be able to delete vertices in the middle, but if I used the swap-back method the index buffer wouldn't be correct anymore.

 

However in my new render class I am using a huge container with all the vertex data of my world and in every frame I assemble a vertex buffer and an index buffer using only the elements of the container that I want to render.(which I will later do with some sort of spatial partitioning) So now only that assembled vertex buffer needs to be in contiguous space and for that I can use a normal STL vector.(since I don't have to delete anything)

 

I will be thinking about what the most optimal container for the vertex pool is.

 

How does getElement know that index is a valid item?  

How are you going to implement iterating through the entire vector?

In my use case I wouldn't have to iterate over the vector except to delete it. And the classes that would use the vector would keep track of all the elements they added and only use getElement for those elements.

 

Even though I probably wont use the class anymore, did I get it right now?

template <typename T>
class Vector
{
private:
	unsigned char* elements;
	int elementCount;
	int elementCapacity;
	int freeElement; // first free element
	int* freeElementList; // each array element is the index of the next free element
	bool* isUsed;
public:
	Vector(int initialCapacity) {
		elementCount = 0;
		elementCapacity = initialCapacity;
		elements = new unsigned char[elementCapacity * sizeof(T)];

		freeElement = 0;
		freeElementList = new int[elementCapacity];
		isUsed = new bool[elementCapacity];
		for (int i = 0; i < elementCapacity; ++i) {
			freeElementList[i] = i + 1;
			isUsed[i] = false;
		}
	}

	~Vector() {
		for (int i = 0; i < elementCapacity; ++i) {
			if (isUsed[i])
				((T*)(elements + i * sizeof(T)))->~T();
		}

		delete [] freeElementList;
		delete [] elements;
		delete [] isUsed;
	}

	int addElement(const T& element) {
		if (elementCount == elementCapacity) {
			elementCapacity *= 2;

			int* oldFreeElementList = freeElementList;
			freeElementList = new int[elementCapacity];
			memcpy(freeElementList, oldFreeElementList, elementCount * sizeof(int));
			delete [] oldFreeElementList;

			bool* oldIsUsed = isUsed;
			isUsed = new bool[elementCapacity];
			memcpy(isUsed, oldIsUsed, elementCount * sizeof(bool));
			delete [] oldIsUsed;

			for (int i = elementCount; i < elementCapacity; ++i) {
				freeElementList[i] = i + 1;
				isUsed[i] = false;
			}

			unsigned char* oldElements = elements;
			elements = new unsigned char[elementCapacity * sizeof(T)];
			for (int i = 0; i < elementCount; ++i) {
				if (isUsed[i])
					new (elements + i * sizeof(T)) T(*((T*)(oldElements + i * sizeof(T))));
			}
			delete [] oldElements;
		}
		new (elements + freeElement * sizeof(T)) T(element);

		isUsed[freeElement] = true;

		int result = freeElement;
		freeElement = freeElementList[freeElement];
		elementCount++;

		return result;
	}

	void removeElement(int index) {
		if (isUsed[index]) {
			((T*)(elements + index * sizeof(T)))->~T();
			freeElementList[index] = freeElement;
			freeElement = index;
			elementCount--;
		}
	}

	T getElement(int index) {
		return *((T*)(elements + index * sizeof(T)));
	}
};

Share this post


Link to post
Share on other sites

You mentioned that you load everything into a vertex buffer. Are you loading non-trivial objects that has to ensure proper constructor and destructor calls into vertex buffers, or are you just over-engineering a container of primitive types?

I never actually used this class for vertex buffers but initially I started managing memory like this because I needed to upload stuff into the vertex buffer. I later thought it was neat and made it into a class without realizing that it wouldn't work correctly if used with non-trivial objects. To fix that I started this post but it's probably more trouble than it's worth.

 

Make elements a T * instead of unsigned char *. Saves you all that pointer casting and sizeof-calls in your pointer arithmetics.

I did that at first, but I adjusted my code according to fastcall22's second response using placement new.

 

Don't relocate objects with the C memory functions. You need to properly copy construct and release objects, or move the objects with the move constructor if you want to take advantage of the move-mechanics in C++11. For trivial objects, this is equivalent as a memory move, but for objects with side effects when constructing/destructing/moving objects, this is highly important to get right. Also ensure that you don't assign to the new memory; you must construct the object in the new memory since otherwise you'll assign something to an non-constructed object.

I think I am currently not relocating objects with C memory functions. I use placement new to copy construct the old objects. But I just noticed that I forgot to call the destructors of the released objects when relocating.

 

It turns out that I don't need this class. If anything I will make a version only for trivial objects. But I learned plenty about placement new, smart pointers, initialization lists and the rule of three/five, so thanks! I appreciate all the responses.smile.png

Edited by Maxjen

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!