Use shared_ptr inheritance rightly when design and use interfaces


If we want to use polymorphism in C++, normally we need to use a base interface pointer to point to an implemented class object. It is quite easy to use in normal pointer in C++. But if we use smart pointer, shared_ptr with the base class type to hold an implemented object, things going to be a little complicated.

Firstly I think it may easy going like this way:

<br />
//Point Base pointer to an implemented Derrived object<br />
//Class &quot;Derived&quot; inheritances from class &quot;Base&quot;<br />
typedef std::shared_ptr&lt;Base&gt; BasePtr;<br />
BasePtr baseptr(new Derived());<br />
//Then call baseptr-&gt;operations<br />

Principle

The principle of using smart pointer to prevent memory leak is always use a named smart pointer variable to hold the result of new

But one thing need to be noted very carefully is that, you cannot use more than ONE shared_ptr to hold the same result of new:

<br />
int* ptr = new int;<br />
shared_ptr&lt;int&gt; p1(ptr);<br />
shared_ptr&lt;int&gt; p2(ptr); //logic error<br />

Because each time we construct a shared_ptr object, the code will maintain 2 pointers in the object:
1. Type <T*> pointer to the object you new in the heap;
2. A “Smart Area” sp_count which holds the reference count of all the shared_ptr objects which hold the <T*>;

Smart Pointer
Each time you use copy constructor or use operate=, shared_ptr will make the reference count maintenance to plus 1 or minus 1; So if there are 2 shared_ptr objects hold the same T*, the pointer <T*> will be delete twice.

This is same when we deal with this pointer. But there is a solution: use shared_from_this():

<br />
#include &lt;memory&gt;<br />
#include &lt;iostream&gt;</p>
<p>struct Good: std::enable_shared_from_this&lt;Good&gt;<br />
{<br />
    std::shared_ptr&lt;Good&gt; getptr() {<br />
        return shared_from_this();<br />
    }<br />
};</p>
<p>struct Bad<br />
{<br />
    std::shared_ptr&lt;Bad&gt; getptr() {<br />
        return std::shared_ptr&lt;Bad&gt;(this);<br />
    }<br />
    ~Bad() { std::cout &lt;&lt; &quot;Bad::~Bad() called\n&quot;; }<br />
};</p>
<p>int main()<br />
{<br />
    // Good: the two shared_ptr's share the same object<br />
    std::shared_ptr&lt;Good&gt; gp1(new Good);<br />
    std::shared_ptr&lt;Good&gt; gp2 = gp1-&gt;getptr();<br />
    std::cout &lt;&lt; &quot;gp2.use_count() = &quot; &lt;&lt; gp2.use_count() &lt;&lt; '\n';</p>
<p>    // Bad, each shared_ptr thinks it's the only owner of the object<br />
    std::shared_ptr&lt;Bad&gt; bp1(new Bad);<br />
    std::shared_ptr&lt;Bad&gt; bp2 = bp1-&gt;getptr();<br />
    std::cout &lt;&lt; &quot;bp2.use_count() = &quot; &lt;&lt; bp2.use_count() &lt;&lt; '\n';<br />
} // UB: double-delete of Bad<br />

 

 Interface Design

When I was trying to design an AbstracktSocket, which can return Socket I/O Streams, users can use I/O Streams smart_ptr to receive/send message through socket, just like the “Java way”:

Main

AbstractSocketImpl implements the interface AbstractSocket, it has the getInputStream() and getOutputStream(), which will return the SocketInputStream and SocketOutputSteam. But AbstractSocketImpl holds shared_ptr of InputStream and OutputStream which implemented from AbstractSocket. SocketInputStream and SocketOutputSteam are constructed by passing AbstractSocketImpl smart_ptr into their Constructors. So when AbstractSocketImpl initialize the Socket I/O Streams, it will share this pointer. To use shared_ptr rightly, we need make AbstractSocketImpl inherit from std::enable_shared_from_this:

 

<br />
InputStreamPtr AbstractSocketImpl::getInputStream()<br />
{<br />
    if ( !inputStreamPtr )<br />
    {<br />
        inputStreamPtr = make_shared&lt;SocketInputStream&gt;(shared_from_this());<br />
    }<br />
    return inputStreamPtr;<br />
}</p>
<p>OutputStreamPtr AbstractSocketImpl::getOutputStream()<br />
{<br />
    if ( !outputStreamPtr )<br />
    {<br />
        outputStreamPtr = make_shared&lt;SocketOutputStream&gt;(shared_from_this());<br />
    }<br />
    return outputStreamPtr;<br />
}<br />

 

You may notice that the inputStreamPtr is a shared_ptr<InputStream> type, but make_shared creates a shared_ptr<SocketInputStream> object. They are not consistent, but the compiler does not returns any error on GNU Compiler and Microsoft Windows Compiler on C++11. There is a conservative way to convert the smart pointer, by using static_pointer_cast<T> or dynamic_pointer_cast<T>:

<br />
inputStreamPtr = static_pointer_cast&lt;InputStream&gt;( make_shared&lt;SocketInputStream&gt;(shared_from_this()) );<br />

 

Single-Inheritance

I have some concern about making AbstractSocketImpl inherit from std::enable_shared_from_this, why not make AbstractSocket inherit from std::enable_shared_from_this cause AbstractSocketImpl already inherits from AbstractSocket. So how to deal with the shared_from_this()? Cause the template types are different between AbstractSocket and AbstractSocketImpl.
The Solution is following:

<br />
class AbstractSocket : boost::noncopyable, public enable_shared_from_this&lt;AbstractSocket&gt; { ... }</p>
<p>class AbstractSocketImpl : public AbstractSocket<br />
{<br />
    std::shared_ptr&lt;AbstractSocketImpl&gt; shared_from_this()<br />
    {<br />
        return std::static_pointer_cast&lt;AbstractSocketImpl&gt;(AbstractSocket::shared_from_this());<br />
    }<br />
}<br />

 

Once using enable_shared_from_this, The object must be created in Heap, NOT in Stack. Because the weak_ptr in enable_shared_from_this should be initialized. Any pointer created in Stack wrapped in shared_ptr will cause Wrong Memory Access:

<br />
// AbstractSocketImpl socketImpl(address);  //----&gt; This is NOT right!<br />
AbstractSocketImplPtr socketImpl = make_shared&lt;AbstractSocketImpl&gt;(address);<br />
InputStreamPtr inputstream = socketImpl-&gt;getInputStream();<br />
OutputStreamPtr outputstream = socketImpl-&gt;getOutputStream();<br />

 

Multiple-Inheritance

There is a topic on Stackoverflow, wich describes the correct usage of multiple inheritance from enabled_share_from_this.

2 comments

Leave a Reply

Your email address will not be published. Required fields are marked *