Reputation: 1953
I have a large group of thousands of objects that I iterate through, in order, tens of thousands to hundreds of thousands (and in some cases, millions) of times. I would like to store these objects contiguously to reduce my number of cache misses. I would also like to get polymorphic behavior out of them. My idea was to allocate a big chunk of memory, construct them in place, and store a pointer to each object in a vector. Some example code looks like this:
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
class Base
{
public:
virtual ~Base() {}
Base(int x) : x_(x) {}
Base& operator=(const Base& b) { x_ = b.x(); return *this; }
inline int x() const { return x_; }
virtual void foo() const { cout << "Base::foo() = " << x() << "\n"; }
private:
int x_;
};
class Derived : public Base
{
public:
~Derived() {}
Derived(int x, int y) : Base(x), y_(y) {}
Derived& operator=(const Derived& d) { Base::operator=(d); y_ = d.y();
return *this; }
inline int y() const { return y_; }
void foo() const { cout << "Derived::foo() = " << x() << ", " << y() <<
"\n"; }
private:
int y_;
};
constexpr size_t max_class_hierarchy_size()
{
return (sizeof(Base) > sizeof(Derived)) ? sizeof(Base) :
sizeof(Derived);
}
enum class DynamicType : unsigned { BASE, DERIVED };
int main()
{
const static unsigned int n = 5;
const int xs[] = {1, 3, 2, -4, 5};
const int ys[] = {-4, 7, 12, 15, 3};
const DynamicType tps[] = {DynamicType::BASE, DynamicType::DERIVED,
DynamicType::DERIVED, DynamicType::DERIVED,
DynamicType::BASE};
cout << "sizeof(Base) = " << sizeof(Base) << "\n";
cout << "sizeof(Derived) = " << sizeof(Derived) << "\n";
cout << "max size() = " << max_class_hierarchy_size() << "\n";
void* main_mem_pool = malloc(n * max_class_hierarchy_size());
Base* mem_pool = static_cast<Base*>(main_mem_pool);
vector<Base*> bs(n);
for (unsigned i = 0; i < n; ++i)
{
bs[i] = mem_pool;
switch(tps[i])
{
case DynamicType::BASE:
{
Base* new_loc_base = static_cast<Base*>(mem_pool);
new (new_loc_base) Base(xs[i]);
new_loc_base++;
mem_pool = static_cast<Base*>(new_loc_base);
break;
}
case DynamicType::DERIVED:
{
Derived* new_loc_derived = static_cast<Derived*>(mem_pool);
new (new_loc_derived) Derived(xs[i], ys[i]);
new_loc_derived++;
mem_pool = static_cast<Base*>(new_loc_derived);
break;
}
default:
cerr << "Type: " << static_cast<unsigned>(tps[i])
<< " not defined. Exitting...\n";
exit(1);
}
}
for (const auto& b : bs) b->foo();
for (int i = n-1; i >= 0; --i) bs[i]->~Base();
free(main_mem_pool);
return 0;
}
I apologize for a lengthy example, but this gives me the behavior I expect. The only issue is that valgrind tells me that I have a memory leak. Valgrinds output says that I have 3 allocations and only 2 deallocations. I don't see it though.
Why is this a memory leak? Is there a better way to solve this problem? Is there an easier way to allocate/deallocate than malloc/free/manually calling destructors?
Upvotes: 1
Views: 53
Reputation: 118425
Running this with valgrind --leak-check=yes --leak-check=full
options, as the original diagnostic from valgrind advised you to do, will show you that the extra allocation comes from some startup code in the C++ standard library, and has nothing to do with your code.
It is quite common for runtime libraries to allocate some static storage at initialization, that they don't bother to release at termination. The original output that you got from valgrind was probably this:
==8498== LEAK SUMMARY:
==8498== definitely lost: 0 bytes in 0 blocks
==8498== indirectly lost: 0 bytes in 0 blocks
==8498== possibly lost: 0 bytes in 0 blocks
==8498== still reachable: 72,704 bytes in 1 blocks
==8498== suppressed: 0 bytes in 0 blocks
Unless something shows up in the first two categories, it's usually not a true memory leak. Most distributions configure valgrind with a suppression file that ignores warnings about known memory chunks that standard system libraries grab at initialization, and don't bother to clean up at exit. Gnome libraries are the worst offenders, and the C++ standard library is usually pretty clean, but I guess that current versions of libstdc++
leave something behind, and the various distro valgrind installs haven't been updated to account for that.
Upvotes: 1