Be careful with hidden cost of std::vector for user defined objects

While creating and using std::vector with user defined classes we needs some special care otherwise it can hamper the performance of our application.


[showads ad=inside_post]
Let’s learn by example,

Suppose we have an Item Class,

class Item {
public:
	static int m_ConstructorCalledCount;
	static int m_DestCalledCount;
	static int m_CopyConstructorCalledCount;
	Item() 	{
		m_ConstructorCalledCount++;
	}
	~Item()	{
		m_DestCalledCount++;
	}
	Item(const Item& obj) {
		m_CopyConstructorCalledCount++;
	}
};
int Item::m_ConstructorCalledCount = 0;
int Item::m_CopyConstructorCalledCount = 0;
int Item::m_DestCalledCount = 0;

And we want to create a vector of 10000 Item objects.
So, let’s create a factory class for it,

class ItemFactory
{
public:
	static std::vector<Item> getItemObjects(int count)
	{
		std::vector<Item> vecOfItems;
		vecOfItems.reserve(count);
		for (int var = 0; var < count; ++var) {
			vecOfItems.push_back(Item());
		}
		return vecOfItems;
	}
};

Now let’s use this factory to create objects,

int count = 10000;
std::vector<Item> vecOfItems;
vecOfItems = ItemFactory::getItemObjects(count);

std::cout<<"Total Item Objects constructed = "<<(Item::m_ConstructorCalledCount + Item::m_CopyConstructorCalledCount)<<std::endl;
std::cout<<"Constructor called  "<<Item::m_ConstructorCalledCount <<" times"<<std::endl;
std::cout<<"Copy Constructor called  "<<Item::m_CopyConstructorCalledCount <<" times"<<std::endl;
std::cout<<"Total Item Objects destructed = "<<Item::m_DestCalledCount<<std::endl<<std::endl;

Above code seems fine, we created 10000 objects of class Item. But while creating these 10000 object we wasted  20000 objects, that’s double of what we actually needed.

Output on g++ 4.8.1,

output_1
Output on g++ 4.8.1

Culprit in this case is the getItemObjects function from ItemFactory class i.e.

   static std::vector<Item> getItemObjects(int count)
	{
		std::vector<Item> vecOfItems;
		vecOfItems.reserve(count);
		for (int var = 0; var < count; ++var) {
			vecOfItems.push_back(Item());
		}
		return vecOfItems;
	}
  • Inside the for loop we created 10000 objects, so constructor is called 10000 times.
  • Then after creating every object we inserted the newly created object in vector 10000 times, therefore copy constructor is called 10000 times and destructor of old 10000 Item object is called.
  • In last line of function, we returned the vector and all its content was copied to vecOfItems vector, so again 10000 times copy constructor is called and destructor of old 10000 Item object is called.

This shows how we wasted 20000 objects.

Now with a small change we can reduce the wasted object count to 10000 from 20000 i.e.

Instead of these 2 lines,

std::vector<Item> vecOfItems;
vecOfItems = ItemFactory::<em>getItemObjects</em>(count);

Use,

std::vector<Item> vecOfItems = ItemFactory::<em>getItemObjects</em>(count);

With this, instead of copying 10000 objects, all objects will be moved to new vector. So, now output will be,

output_2
Output on g++ 4.8.1

 

So, with this instead of 20000 we wasted only 10000 objects.

But still we are wasting 10000 objects. How to fix that?

We can do this by 2 ways,

1.) Instead of returning the whole new vector from factory function, just passing the new vector as a reference to factory function i.e.

	static void getItemObjects_1(std::vector<Item> & vecItems, int count)
	{
		vecItems.assign(count, Item());
	}

It’s output will be,

output_3

 

So, now we are just wasting 1 object.

2.) Just use the vector’s assign function to create the 10000 copies of 1 object i.e.

static std::vector<Item> getItemObjects_2( int count)
	{
		std::vector<Item> vecOfItems;
		vecOfItems.assign(count, Item());
		return vecOfItems;
	}

It’s output will be,

output_3

So, with this too we are just wasting 1 object.

Similarly, we should also avoid passing vector to functions by value whenever possible to avoid this kind of memory wastage

Complete code is as follows,

#include <iostream>
#include <vector>

class Item {
public:
	static int m_ConstructorCalledCount;
	static int m_DestCalledCount;
	static int m_CopyConstructorCalledCount;
	Item() 	{
		m_ConstructorCalledCount++;
	}
	~Item()	{
		m_DestCalledCount++;
	}
	Item(const Item& obj) {
		m_CopyConstructorCalledCount++;
	}
};
int Item::m_ConstructorCalledCount = 0;
int Item::m_CopyConstructorCalledCount = 0;
int Item::m_DestCalledCount = 0;

class ItemFactory
{
public:
	static std::vector<Item> getItemObjects(int count)
	{
		std::vector<Item> vecOfItems;
		vecOfItems.reserve(count);
		for (int var = 0; var < count; ++var) {
			vecOfItems.push_back(Item());
		}
		return vecOfItems;
	}
};

class ItemFactoryImproved
{
public:
	static void getItemObjects_1(std::vector<Item> & vecItems, int count)
	{
		vecItems.assign(count, Item());
	}
	static std::vector<Item> getItemObjects_2( int count)
	{
		std::vector<Item> vecOfItems;
		vecOfItems.assign(count, Item());
		return vecOfItems;
	}
};

int main()
{

	int count = 10000;

	std::vector<Item> vecOfItems;
	vecOfItems = ItemFactory::getItemObjects(count);

	std::cout<<"Total Item Objects constructed = "<<(Item::m_ConstructorCalledCount + Item::m_CopyConstructorCalledCount)<<std::endl;
	std::cout<<"Constructor called  "<<Item::m_ConstructorCalledCount <<" times"<<std::endl;
	std::cout<<"Copy Constructor called  "<<Item::m_CopyConstructorCalledCount <<" times"<<std::endl;
	std::cout<<"Total Item Objects destructed = "<<Item::m_DestCalledCount<<std::endl<<std::endl;

	Item::m_ConstructorCalledCount = 0;
	Item::m_CopyConstructorCalledCount = 0;
	Item::m_DestCalledCount = 0;
	std::vector<Item> vecOfItems_2 = ItemFactory::getItemObjects(count);
	std::cout<<"Total Item Objects constructed = "<<(Item::m_ConstructorCalledCount + Item::m_CopyConstructorCalledCount)<<std::endl;
	std::cout<<"Constructor called  "<<Item::m_ConstructorCalledCount <<" times"<<std::endl;
	std::cout<<"Copy Constructor called  "<<Item::m_CopyConstructorCalledCount <<" times"<<std::endl;
	std::cout<<"Total Item Objects destructed = "<<Item::m_DestCalledCount<<std::endl<<std::endl;

	Item::m_ConstructorCalledCount = 0;
	Item::m_CopyConstructorCalledCount = 0;
	Item::m_DestCalledCount = 0;
	std::vector<Item> vecOfItems_3 = ItemFactoryImproved::getItemObjects_2(count);
	std::cout<<"Total Item Objects constructed = "<<(Item::m_ConstructorCalledCount + Item::m_CopyConstructorCalledCount)<<std::endl;
	std::cout<<"Constructor called  "<<Item::m_ConstructorCalledCount <<" times"<<std::endl;
	std::cout<<"Copy Constructor called  "<<Item::m_CopyConstructorCalledCount <<" times"<<std::endl;
	std::cout<<"Total Item Objects destructed = "<<Item::m_DestCalledCount<<std::endl<<std::endl;

	Item::m_ConstructorCalledCount = 0;
	Item::m_CopyConstructorCalledCount = 0;
	Item::m_DestCalledCount = 0;
	std::vector<Item> vecOfItems_4;
	ItemFactoryImproved::getItemObjects_1(vecOfItems_4, count);
	std::cout<<"Total Item Objects constructed = "<<(Item::m_ConstructorCalledCount + Item::m_CopyConstructorCalledCount)<<std::endl;
	std::cout<<"Constructor called  "<<Item::m_ConstructorCalledCount <<" times"<<std::endl;
	std::cout<<"Copy Constructor called  "<<Item::m_CopyConstructorCalledCount <<" times"<<std::endl;
	std::cout<<"Total Item Objects destructed = "<<Item::m_DestCalledCount<<std::endl<<std::endl;
}

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top