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

3

6

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.

Persuader answered 12/3, 2012 at 21:42 Comment(1)
By looking at boost's <make_shared.hpp>, it's using the global placement new operator ::new( pv ) T(). That's why your class level placement is not being called... By remove the global qualifier '::' before new, the make_shared actually calls your class level placement new operator.Crackleware
N
8

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.

Newsmonger answered 12/3, 2012 at 22:16 Comment(2)
Worth noting that allocate_shared() could mean replacing quite a few calls in a bigger codebase.Waziristan
Thanks for the useful answer. I did not know I was not supposed to override placement new. So this is definitely a showstopper for my attempt at a solution. I did already investigate the allocate_shared option, but -as Georg stated- this has a too high impact on the existing code.Persuader
W
4

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.

Waziristan answered 12/3, 2012 at 22:14 Comment(0)
C
3

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.

Cateyed answered 12/3, 2012 at 22:26 Comment(1)
Despite that, it is still possible to provide a per-class overload even for placement-new. Unusual perhaps, but then again, which part of C++ isn't unusual :-) The main point to take home is that std::allocator doesn't use any per-class allocation functions, but only the global ::new.Swinney

© 2022 - 2024 — McMap. All rights reserved.