Reputation: 23619
I'm starting to use shared pointers in C++ (Visual Studio 2010) and run into the following problem.
I am writing a new module which defines an interface that tells the module how to behave in certain conditions. It's something like this (artifical example just to illustrate my problem):
// Interface that should be implemented by user of the module
class RingAlert
{
public:
virtual void ring() = 0;
};
// Module that does something important
class Module
{
public:
Module (RingAlert &ringAlert) : m_ringAlert(ringAlert) {}
void dosomething(); // may call RingAlert::ring if something goes wrong.
private:
RingAlert &m_ringAlert;
};
To make it easy for the users of the module, and because the RingAlert could be passed to other modules as well, I am now making this a shared pointer, like this:
typedef std::shared_ptr<RingAlert> RingAlertPtr;
class Module
{
public:
Module (RingAlertPtr ringAlert) : m_ringAlert(ringAlert) {}
void dosomething(); // may call RingAlert::ring if something goes wrong.
private:
RingAlertPtr m_ringAlert;
};
Now users of the module may create a new RingAlert instance and simply pass it to the module, without keeping it somewhere and deleting it at the end of the application.
The problem starts if the application does something like this:
class MyRingAlert : public RingAlert
{
public:
virtual void ring() {std::cout << "ring ring" << std::endl;}
};
class Application
{
public:
private:
MyRingAlert m_myRingAlert;
};
// later, somewhere in application code
Module m(RingAlertPtr(&m_myRingAlert));
In this example, the application takes the address of a data member and puts it in a shared pointer. Later in the application, the destructor of the Module will delete the shared pointer, which will decrement the reference count, and which will then delete the ring alert, which mustn't be deleted because it is a data member of the Application class.
I found a way of preventing the shared pointer from deleting the instance by doing this (using lambda's but also have a slightly cleaner solution using a function):
Module m(RingAlertPtr(&m_myRingAlert,[](void *){});
Although this solves my problem, I'm not completely happy with it because it will still give a problem if the Application class is destructed before the Module class.
The only good solution seems to be to force the rest of the application to new the instance of RingAlert (or actually the class that implements RingAlert).
Can this be done? Is there a way to prevent code from instantiating subclasses of a base class on the stack or as datamember? In other words: can we force subclasses of a base class being newed if they want to be instantiated?
Upvotes: 1
Views: 2039
Reputation: 146940
This is fundamentally flawed- what if I want my main class to inherit and provide that callback? You're artificially limiting it for no real advantage. Use a std::function
instead.
class Module {
public:
Module (std::function<void()> ringAlert) : Alert(ringAlert) {}
void dosomething() {
if (something) alert();
}
private:
std::function<void()> alert;
};
The Standard function type is exceedingly flexible and very good to use.
You could also provide for a custom destruction function.
class Module {
public:
Module(interface* ptr, std::function<void(interface*)> destructor)
:pointer(ptr), destruct(destructor) {
}
~Module() {
if (destruct)
destruct(pointer);
}
void doSomething() {
if (condition)
pointer->ring();
}
private:
interface* pointer;
std::function<void(interface*)> destruct;
};
If I alloc on the stack, I pass an empty function object. If I allocate on the heap, I pass a quick destruction lambda.
Upvotes: 3
Reputation: 15872
Make the constructor for RingAlert
private and create a static method that calls new
and returns the pointer (or better yet, returns the shared_ptr
).
Upvotes: 0
Reputation: 25799
What I tend to do - and it's by no means the right way - or even the best way is as follows:
struct IType
{
typedef shared_ptr<IType> Ptr;
virtual DoSomething( ) = 0;
protected:
virtual ~IType( ) { }
};
class Type : public IType
{
public:
typedef shared_ptr<Type> Ptr;
static Ptr New( )
{
return Ptr(
new Type( ),
&Type::Delete
);
}
private:
virtual void DoSomething( );
Type( ); // Defined in the CPP file.
static void Delete( Type* p )
{
delete ( p );
}
};
So you can only ever create an object of type Type::Ptr, You cannot delete the enclosed type through the .get() method.
Hope this helps.
Upvotes: 1
Reputation: 17258
I believe the usual solution to make the constructor(s) protected
and only allow creation through static
(or free friend
functions) returning shared_ptr<>
. Since the constructor is protected, non-friend code cannot create instances, whether that code tries to construct the instance on the stack or, in a constructor, as a member.
You have to ensure that child classes also make their constructors protected, of course.
Upvotes: 0
Reputation: 23624
What about to create private type-depended overrides operator new
and delete
for class RingAlert
Upvotes: 0