3

Using gcc 4.6.2, make_shared() gives a useless backtrace (apparently due to some rethrow) if a constructor throws an exception. I'm using make_shared() to save a bit of typing, but this is show stopper. I've created a substitute make_shrd() that allows a normal backtrace. I'm using gdb 7.3.1.

I'm worried that:

  1. The bad backtrace under make_shared() is somehow my own fault
  2. My substitute make_shrd() will cause me subtle problems.

Here's a demo:

#include <memory>
#include <stdexcept>

using namespace std;

class foo1
{
public:
        foo1( const string& bar, int x ) :m_bar(bar), m_x(x)
        {
                throw logic_error( "Huh?" );
        }
        string m_bar;
        int m_x;
};

class foo2
{
public:
        foo2( const string& bar, int x ) : m_foo1(bar,x)
        {}

        foo1  m_foo1;
};

// more debuggable substitute for make_shared() ??
template<typename T, typename... Args>
std::shared_ptr<T> make_shrd( Args... args )
{
        return std::shared_ptr<T>( new T(args...));
}

int main()
{
        auto p_foo2 = make_shared<foo2>( "stuff", 5 );          // debug BAD!!
//      auto p_foo2 = make_shrd<foo2>( "stuff", 5 );            // debug OK
//      auto p_foo2 = new foo2( "stuff", 5 );                   // debug OK
//      auto p_foo2 = shared_ptr<foo2>(new foo2( "stuff", 5 )); // debug OK
        return (int)(long int)p_foo2;
}

Compiled with:

g++ -g -std=c++0x -Wall -Wextra main.cpp

Debugged with:

gdb a.out

The make_shared() backtrace is junk that does not show the stack to the point of the exception. All the other options provide a sane backtrace.

Thanks in advance for help and suggestions.

srking
  • 4,512
  • 1
  • 30
  • 46
  • You need perfect forwarding for a start... Add `&&` and `std::forward<>` to `make_shrd` and it should be fine, though you're still missing out on the allocation optimization provided by `std::make_shared`. – ildjarn Feb 10 '12 at 19:14
  • 1
    Ultimately this just seems like a QOI issue in GCC 4.6. Have you tried GCC 4.7 yet? – ildjarn Feb 10 '12 at 19:33
  • 2
    See here http://stackoverflow.com/questions/9135144/c-make-shared-not-available/9136228#9136228 for a simple roll-your-own make_shared – Gigi Feb 10 '12 at 19:36
  • @Gigi, The backtrace with your version is fine. Thank you. – srking Feb 10 '12 at 19:58

1 Answers1

4

Your implementation of make_shrd() looses the ability to allocate just one chunk of memory: std::make_shared() does two things:

  1. it avoids duplicating of writing the type (if the type of the allocation and the type of the desired std::shared_ptr<T> are the same rather than the latter being for a base class)
  2. it combines allocation of the shared object and the object's descriptor into just one allocation

The main purpose of std::make_shared() is actually the second feature. I haven't looked at the implementation but I suspect that this is also the part which actually causes you problems. Other than that, I don't see any reason why your implementation is any worse once you fix forwarding of arguments:

template<typename T, typename... Args>
std::shared_ptr<T> make_shrd(Args&&... args)
{
    return std::shared_ptr<T>(new T(std::forward<Args>(args)...));
}
Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 1
    Note that the combining of the allocation of the shared block is not *mandated* by the specification. Virtually every implementation will do it, but it's not *required*. Of course, having shared block at all is not technically required either, but every `shared_ptr` implementation does. – Nicol Bolas Feb 10 '12 at 22:48
  • 1
    There is a third reason for `make_shared`: It avoids memory leaks. Consider a function call `f(std::shared_ptr(new X(...)), std::shared_ptr(new Y(...)))`. The compiler could execute `new X(...)` first, then `new Y(...)` _before_ passing the `X*` to the `std::shared_ptr`. When `new Y(...)` throws an exception, you have a memory leak. Replace it with `f(std::make_shared(...), std::make_shared(...))` and the (possible) leak is gone. – Daniel Frey Feb 18 '13 at 20:35