防止类在堆栈上实例化或作为数据成员

时间:2011-01-19 13:28:17

标签: c++ shared-ptr

我开始在C ++(Visual Studio 2010)中使用共享指针并遇到以下问题。

我正在编写一个新模块,它定义了一个接口,告诉模块在某些条件下如何表现。它是这样的(人为的例子只是为了说明我的问题):

// 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;
   };

为了方便模块的用户,并且因为RingAlert也可以传递给其他模块,我现在将它作为共享指针,如下所示:

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;
   };

现在,模块的用户可以创建一个新的RingAlert实例,只需将其传递给模块,而无需将其保留在某处并在应用程序结束时将其删除。

如果应用程序执行以下操作,问题就会出现:

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));

在此示例中,应用程序获取数据成员的地址并将其放入共享指针中。 稍后在应用程序中,模块的析构函数将删除共享指针,这将减少引用计数,然后删除响铃警报,由于它是数据,因此不能删除Application类的成员。

我找到了一种方法来防止共享指针通过执行此操作来删除实例(使用lambda,但使用函数也有一个稍微更清晰的解决方案):

Module m(RingAlertPtr(&m_myRingAlert,[](void *){});

虽然这解决了我的问题,但我对它并不完全满意,因为如果Application类在Module类之前被破坏,它仍会产生问题。

唯一的好解决方案似乎是强制应用程序的其余部分 new RingAlert实例(或实际上是实现RingAlert的类)。

可以这样做吗? 有没有办法阻止代码在堆栈或datamember上实例化基类的子类? 换句话说:如果想要实例化基类的子类,我们可以强制它吗?

5 个答案:

答案 0 :(得分:3)

这基本上是有缺陷的 - 如果我希望我的主类继承并提供回调怎么办?你是人为地限制它没有真正的优势。请改用std::function

class Module {
public:
    Module (std::function<void()> ringAlert) : Alert(ringAlert) {}
    void dosomething() {
        if (something) alert();
    }  
private:
    std::function<void()> alert;
};

标准功能类型非常灵活,非常好用。

您还可以提供自定义销毁功能。

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;
};

如果我在堆栈上分配,我传递一个空的函数对象。如果我在堆上分配,我会传递一个快速破坏lambda。

答案 1 :(得分:1)

我倾向于做什么 - 这绝不是正确的方式 - 甚至最好的方式如下:

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 );
    }
};

因此,您只能创建Type :: Ptr类型的对象,不能通过.get()方法删除所附类型。

希望这有帮助。

答案 2 :(得分:0)

如何为类RingAlert创建私有类型依赖覆盖运算符newdelete

答案 3 :(得分:0)

我相信制作构造函数protected的常用解决方案只允许通过static(或免费的friend函数)创建返回shared_ptr<>。由于构造函数受到保护,非友元代码无法创建实例,无论该代码是尝试在堆栈上构造实例,还是在构造函数中构建成员。

当然,您必须确保子类也使其构造函数受到保护。

答案 4 :(得分:0)

使RingAlert的构造函数为private,并创建一个调用new的静态方法并返回指针(或更好的是,返回shared_ptr)。