Tags

,

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.

Advertisements