Defective Language

Safely using enable_shared_from_this

Obtaining a ‘shared_ptr’ from ‘this’ is possible using the ‘enable_shared_from_this’ class. It’s a feature that allows a class to reference itself within a smart pointer based API. Beware an ugly implementation detail that renders their natural use somewhat unsafe: the ‘shared_ptr’ itself is not automatically created. Calling ‘shared_from_this’ on a non-shared object results in undefined behaviour.

void register_service( shared_ptr<service> );

class service : public enable_shared_from_this<service> {
public:
	void register() {
		register_service( shared_from_this() );
	}
}

//intended use
auto good = make_shared<service>();
good->register();

//bad use
service bad;
bad.register(); //undefined behaviour on 'shared_from_this'

//also bad
auto pbad = new service();
pbad->register();

‘enable_shared_from_this’ requires the object be owned by a ‘shared_ptr’, either via a call to ‘make_shared’ or a ‘shared_ptr’ constructor. This requirement is unfortunately never checked: if the object is not owned by a ‘shared_ptr’ no compile-time, nor run-time error will be generated. The code ventures into the land of undefined behaviour.

Privatize the constructors

Blocking invalid creation is achieved by making the class constructors private. This prevents any direct instantiation. Instead a new ‘create’ method does the construction on our behalf, returning an appropriate ‘shared_ptr’.

class service : public enable_shared_from_this<service> {
	service() = default;
public:
	static shared_ptr<service> create() {
		return shared_ptr<service>( new service );
	}
};

//only available use (and it's valid)
auto good = service::create();

//compile-time failures
auto fail = new service();
service mfail;

The example works, but not all possible constructors are covered. At a minimum the copy constructor must also be made private. Typically a user defined constructor will also be needed. It would be unfortunate if every constructor needed a matching ‘create’ method. Prior to C++11 that was the case, but now we can use variadic templates and parameter forwarding.

class service : public enable_shared_from_this<service> {
	service() = default;
	service( const service & o ) = default; // or = delete if you want
	service( config_settings c );
public:
	template<typename ... T>
	static shared_ptr<service> create( T&& ... all ) {
		return shared_ptr<service>( new service( std::forward<T>(all)... ) );
	}
};

This ‘create’ method takes any number of parameters and passes them to one of the private constructors. The ‘…’ is part of the variadic template syntax. The ‘&&’ and ‘std::forward’ are part of the universal reference and perfect forwarding system. It’s definitely worth the time to learn those C++ features.

make_shared

It might be tempting to use ‘make_shared’ as is common when creating shared objects. It cannot be used here due to visibility rules. Since ‘make_shared’ is not a member function of ‘service’ it doesn’t have access to the private constructors. I don’t suspect much is lost; the purpose of ‘make_shared’ is a small memory optimization. I would hope that marking a class ‘enable_shared_from_this’ is also enough to get the same optimization. It is almost certainly not a relevant detail in most cases where ‘enable_shared_from_this’ is used.

Visibility rules do however create another issue: a generic template that implements this ‘create’ function can’t be built. It must be copied into each class that needs it; a base class is unable to access the private constructors needed to implement ‘create’. To remove the duplication a macro will be required.

Categories: Defective Language, Programming

Tagged as: ,

7 replies »

  1. I tried making make_shared a friend but it wouldn’t give in; GCC 4.8.

    Any idea why? So far I like the idea of a shared_ptr only implementable class, it would guarantee safety.

    • The problem is not knowing what functions make_shared actually calls. If it called ‘new’ directly a friend function would work. It could however call ‘new’ in some sub-function it uses, in which case your friend declaration will do nothing.

      There is of courses a tagging technique, where you append a private structure to each constructor and also pass it to make_shared. This works, but has the drawback you can’t use any default constructors.

  2. Two years later..

    I also wanted to be able to use make_shared. I found a hack that seems to work, but it introduces an extra layer of indirection and some duplication of code. Anyway, I think should be possible to expand this example to cover all sorts of constructors as well.

    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    class Element : public enable_shared_from_this<Element>{
        
        private:
        // disable copy constructor/assignment.
        // (also implicitly disables default constructor)
        Element(Element &other);
        Element &operator=(Element &other);
    
        // make a private default constructor; we want the 'create'-method to
        // be the only way of instanciating this class.
        Element(){;}
    
        // Nested class, should inherit from Element, so it must be
        // defined outside the Element class. Note that this class is private
        // to the Element class, and also a friend of the Element class.
        class ElementConstructor;
     
        public:
        // create an instance of Element the way we want it to be created.
        static shared_ptr<Element> create(){ 
            return static_pointer_cast<Element>
                (make_shared<ElementConstructor>());
        }
    };
    typedef shared_ptr<Element> ElementPtr;
    
    class Element::ElementConstructor : public Element{
        public:
        ElementConstructor(){;}
    };
    
    int main()
    {
       ElementPtr element = Element::create();
       return 0;
    }
    
    • Why the `static_pointer_cast` in `create`, it shouldn’t be necessary?

      You can use the `Element(Element &other) = delete;` syntax to delete a standard constructor.

      But I think my code should have covered your example as well, or does it not covere the zero parameters case? I used variadic templates to try and cover all cases.

    • I think the cast is necessary because of the way I use the ‘ElementConstructor’ as template argument to make_shared. This makes make_shared return a shared_ptr. Since we want the ‘create’-method to return a shared_ptr, and since there are no implicit conversion, we need to do the cast.

      Yeah, surely I could use the ‘Element(Element &other) = delete;’ syntax :) I’m just kind of more used to declare it privately, since in effect I guess it does the same, but it’s also legal in c++99.

      Your code is more general in terms of numbers of parameters that can be passed to create :) It does take the zero-parameters case as well. After posting my code to you, I tried to change my code to be as general as yours, with your code as example, and it worked :)

      The absolutely only thing I wanted to demonstrate was that it Is possible to figure a way where Elements constructor is private, and also use make_shared in the ‘create’-method, as opposed to using shared_ptr(new Element()). Now it’s okay to question how interesting this actually is, though. make_shared do have some benefits to it, but my code doesn’t exactly adhere so much to the ‘kiss’-principle as your code does.

  3. using make_shared

    #include <iostream>
    #include <memory>
    
    class A : public std::enable_shared_from_this<A>
    {
    private:
        A(){}
        explicit A(int a):m_a(a){}
    public:
        template <typename... Args>
        static std::shared_ptr<A> create(Args &&... args)
        {
            class make_shared_enabler : public A
            {
            public:
                make_shared_enabler(Args &&... args):A(std::forward<Args>(args)...){}
            };
            return std::make_shared<make_shared_enabler>(std::forward<Args>(args)...);
        }
    
        int val() const
        {
            return m_a;
        }
    private:
        int m_a=0;
    };
    
    int main(int, char **)
    {
        std::shared_ptr<A> a0=A::create();
        std::shared_ptr<A> a1=A::create(10);
        std::cout << a0->val() << " " << a1->val() << std::endl;
        return 0;
    }
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s