Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

boost::make_shared is not calling (placement) operator new?

I am using boost::make_shared for the first time to create objects pointed to by shared pointers. Mainly because our code was too slow and the single allocation really helped to improve performance.

After fixing some memory leaks "the hard manual way" I decided to implement a simple memory leak detector by overriding new operators for all relevant classes just for counting which objects are still alive at specific points in our application. I have implemented this several times before and was surprised to find my code no longer detects any objects.

I figured that all I had to do is override "placement new" instead of the "normal" operator new's because of the following from the boost website documentation for make_shared:

"Effects: Allocates memory suitable for an object of type T and constructs an object in it via the placement new expression new( pv ) T() or new( pv ) T( std::forward(args)... ). allocate_shared uses a copy of a to allocate memory. If an exception is thrown, has no effect."

My placement new is also not being called however. I have written a small test program to reproduce the behavior:

#include <iostream>
using namespace std;
#include "boost/shared_ptr.hpp"
#include "boost/make_shared.hpp"

class Test
{
public:
    Test() { cout << "Test::Test()" << endl; }

    void* operator new (std::size_t size) throw (std::bad_alloc) {
        cout << "Test new" << endl;
        return malloc(size);
    }

    void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw() {
        cout << "Test non-throwing new" << endl;
        return malloc(size);
    }

    void* operator new (std::size_t size, void* ptr) throw() {
        cout << "Test non-throwing placement new" << endl;
        return malloc(size);
    }
};

void* operator new (std::size_t size) throw (std::bad_alloc) {
    cout << "Global new" << endl;
    return malloc(size);
}

int main() {
    cout << "..." << endl;
    boost::shared_ptr<Test> t1(boost::make_shared<Test>());
    cout << "..." << endl;
    boost::shared_ptr<Test> t2(new Test());
    cout << "..." << endl;

    return 0;
}

Which renders the following output:

...
Global new
Test::Test()
...
Test new
Test::Test()
Global new
...

I was expecting "Test non-throwing placement new" on the 3rd line of output. What do you think the behavior should be? Do you agree that according to documentation of make_shared it should call the placement new operator of my Test class? Or did I misunderstand it?

I could copy boosts implementation locally and add a call to the placement new operator of course. But, would that be appropriate, or would it violate the intended semantics of placement new?

Thanks in advance for your time and your help.

like image 719
Joost Sannen Avatar asked Mar 12 '12 21:03

Joost Sannen


3 Answers

Looking as the source of make_shared, it uses the global placement new operator, instead of the new operator supplied by your class.

::new( pv ) T();

Unfortunately (as least on OS X) (according to the standard), you cannot define your own global placement new operator. It appears that allocate_shared is more along the lines of what you're looking for.

Edit:

An alternative could be to actually write a version of make_shared which uses the class's placement new instead of the global one. It's only about 10 lines of code, and should be fine so long as you honor the license of the original code.

like image 95
leedm777 Avatar answered Sep 20 '22 22:09

leedm777


You may not replace placement new (§18.4.​1.3, see e.g. this question), so the output given seems fine.

As an alternative to modifying the Boost headers, you could look into external tools like Valgrind.

like image 31
Georg Fritzsche Avatar answered Sep 19 '22 22:09

Georg Fritzsche


Your operator new implemented for your particular type will only be used on expressions on which elements of your type is dynamically allocated with new, such as Test *p = new Test;. Now make_shared does not dynamically allocate an object of your type, but rather a buffer that holds enough information for the shared count (which includes the counter, deleter and a few extra bits and pieces) and your object.

It then uses placement-new to call the constructor of your object. Note that placement new in this case is not allocating memory, it is only the funny syntax in C++ to call the constructor on a block of already allocated memory. This might actually be the source of confusion, as the new expression, your operator new and placement-new are three different concepts that happen to share a name.

like image 40
David Rodríguez - dribeas Avatar answered Sep 20 '22 22:09

David Rodríguez - dribeas