Debuggable replacement for make_shared()
Asked Answered
P

1

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.

Plaything answered 10/2, 2012 at 19:8 Comment(4)
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.Thimerosal
Ultimately this just seems like a QOI issue in GCC 4.6. Have you tried GCC 4.7 yet?Thimerosal
See here https://mcmap.net/q/82711/-c-make_shared-not-available/… for a simple roll-your-own make_sharedMusetta
@Gigi, The backtrace with your version is fine. Thank you.Plaything
U
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)...));
}
Unroot answered 10/2, 2012 at 20:40 Comment(2)
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.Introduction
There is a third reason for make_shared: It avoids memory leaks. Consider a function call f(std::shared_ptr<X>(new X(...)), std::shared_ptr<Y>(new Y(...))). The compiler could execute new X(...) first, then new Y(...) before passing the X* to the std::shared_ptr<X>. When new Y(...) throws an exception, you have a memory leak. Replace it with f(std::make_shared<X>(...), std::make_shared<Y>(...)) and the (possible) leak is gone.Blastosphere

© 2022 - 2024 — McMap. All rights reserved.