当构造函数是私有的时,使用公共析构函数

9
我曾经看到过构造函数被声明为私有,而析构函数被声明为公共的代码。这样的声明有什么用处?析构函数需要公开吗,以便在继承期间可以进行调用,还是代码中存在错误?
问题可能缺少一些信息,但我真正想知道的是,当构造函数需要是私有的时候,拥有公共析构函数是否符合C ++规则?

1
一个类只有一个析构函数,但它可以有多个构造函数(默认、复制、移动(自C++11起),以及可能更多用户定义的)。在你看到的代码中,_所有_构造函数都声明为私有的吗?此外,它们是被定义了还是未定义?另外,你提到了继承:这个类是基类吗?(你可以添加一个代码片段来说明) - gx_
构造函数都是私有的;默认构造函数已经被定义。我认为我不能提供代码片段,因为它是行业代码,但是我认为问题中的信息足够了。 - Sankalp
如果您没有至少protected访问权限来访问基类的构造函数,那么您如何从基类“继承”?关于为什么有人会将构造函数设置为私有,一个常见的情况是单例模型类(呃,这个单例真是个脏词)。声明了一个相同类类型的静态类变量。它可以构造,但其他人不能。 - WhozCraig
关于私有析构函数是否被允许,我不认为这篇文章能够解释清楚 - WhozCraig
仅查看类的定义是不够的,还需要了解它实际被使用的方式(外部代码如何获取实例或指向实例的指针,何时触发销毁),这样才能更好地理解其原因。在“行业代码”方面,如果只是为了说明问题,可以编写一个简化的示例(例如使用名称“Foo”和简短的实现)。 - gx_
6个回答

9

简短回答

将构造函数设为私有但析构函数设为公共的做法有很多实际用途。

你可以使用这种模式来:

详细回答

上面我提到,您可以使用私有构造函数和析构函数来实现几个设计模式。接下来就是如何实现...

引用计数

在对象中使用私有析构函数有助于引用计数系统。这使得开发人员能够更好地控制对象的生命周期。

class MyReferenceObject
{
public:
    static MyReferenceObject* Create()
    {
        return new MyReferenceObject();
    }

    void retain()
    {
        m_ref_count++;
    }

    void release()
    {
        m_ref_count--;
        if (m_ref_count <= 0)
        {
            // Perform any resource/sub object cleanup.
            // Delete myself.
            delete this; // Dangerous example but demonstrates the principle.
        }
    }
private:

    int m_ref_count;

    MyReferenceObject()
    {
        m_ref_count = 1;
    }

    ~MyReferenceObject() { }

}

int main()
{
    new MyReferenceObject(); // Illegal.
    MyReferenceObject object; // Illegal, cannot be made on stack as destructor is private.

    MyReferenceObject* object = MyReferenceObject::Create(); // Creates a new instance of 'MyReferenceObject' with reference count.
    object->retain(); // Reference count of 2.
    object->release(); // Reference count of 1.
    object->release(); // Reference count of 0, object deletes itself from the heap.
}

这展示了一个对象如何管理自己并防止开发人员破坏内存系统。请注意,这是一个危险的例子,因为MyReferenceObject会删除自己,在此处查看需要考虑的事项清单。 单例模式 一个单例类中的私有构造函数和析构函数的主要优点是强制用户只能按照代码设计的方式使用它。无法创建一个流氓单例对象(因为它在编译时被强制执行),用户也无法删除单例实例(同样是在编译时被强制执行)。
例如:
class MySingleton
{
public:
     MySingleton* Instance()
     {
        static MySingleton* instance = NULL;
        if (!instance)
        {
            instance = new MySingleton();
        }

        return instance;
     }
private:
    MySingleton() { }
    ~MySingleton() { } 
}

int main()
{
     new MySingleton(); // Illegal
     delete MySingleton::Instance(); // Illegal.
}

看看代码几乎不可能被误用。在编译时强制执行MySingleton的正确使用,从而确保开发人员必须按预期使用MySingleton

工厂模式

在工厂设计模式中使用私有构造函数是一种重要机制,以强制仅使用工厂创建对象。

例如:

class MyFactoryObject
{
public:

protected:
    friend class MyFactory; // Allows the object factory to create instances of MyFactoryObject

    MyFactoryObject() {} // Can only be created by itself or a friend class (MyFactory).
}

class MyFactory
{
public:
    static MyFactoryObject* MakeObject()
    {

        // You can perform any MyFactoryObject specific initialisation here and it will carry through to wherever the factory method is invoked.
        return new MyFactoryObject();
    }
}

int main()
{
    new MyFactoryObject(); // Illegal.
    MyFactory::MakeObject(); // Legal, enforces the developer to make MyFactoryObject only through MyFactory.
}

这是强大的,因为它将MyFactoryObject的创建隐藏在开发者背后。你可以使用工厂方法来执行任何MyFactoryObject的初始化(例如:设置GUID,注册到数据库),并且无论在何处使用工厂方法,该初始化代码也将被执行。
概要
这只是一些示例,展示了如何使用私有构造函数和析构函数来强制执行API的正确使用。如果您想要变得狡猾,还可以将所有这些设计模式结合起来使用 ;)

你的示例中有很多错误,比如你的Singleton::Instance()返回的是一个MySingleton的值,而不是引用或指针。你声明的静态实例变量没有类型。同样,你的MakeObject()方法返回的是一个值,但试图返回一个指针。 - Andre Kostur
谢谢您指出这些问题。我会修正答案的(我花了太多时间在C#领域)。 - matthewrdev

4

首先:析构函数可以是私有的。

当要求构造函数为私有时,拥有公共析构函数是否符合C++规则?

在C++中完全可行。事实上,一个很好的例子就是单例模式,其中构造函数是私有的,而析构函数是公共的。


1
没错。单例就是我在问这个问题时想到的东西。但是,在任何地方解释singleton时,析构函数都被设为私有的,这激起了更多的疑问和问题。 - Sankalp

2

倒序。

析构函数是否需要公开,以便在继承期间可以进行调用,还是代码中存在错误?

实际上,为了使继承起作用,析构函数至少应该是protected。如果您从具有私有析构函数的类继承,则无法为派生类生成析构函数,这实际上会防止实例化(您仍然可以使用static方法和属性)。

这种声明有什么用途?

请注意,即使构造函数是私有的,如果没有进一步指示,该类也具有(默认生成的)公共复制构造函数和复制赋值运算符。这种模式经常出现在:

  • 命名构造函数惯用法
  • 工厂

命名构造函数惯用法的示例:

class Angle {
public:
    static Angle FromDegrees(double d);
    static Angle FromRadian(double d);

private:
    Angle(double x): _value(x) {}
    double _value;
};

由于不确定 x 应该用度数还是弧度(或其他单位),构造函数被设置为 private,并提供了 命名方法。这样,使用方法可以明确单位:

Angle a = Angle::FromDegrees(360);

1

如果您想防止创建类的多个实例,则可以将构造函数设置为私有。这样,您就可以控制实例的创建而不是销毁。因此,析构函数可以是公共的。


1
我会完全删除第一句话,因为它是不正确的。 - rbaleksandar
为了阐述@rbaleksandar的评论,仅仅将构造函数设置为私有并不能防止创建多个类实例。你仍然需要做一些工作来实现这一点。例如,你可能需要编写一个静态工厂方法来确保最多只构造一个类实例。将构造函数设置为私有将强制用户调用静态方法来获取实例。此外,你的回答并没有明确说明这是唯一需要将构造函数设置为私有的情况。实际上并非如此。 - Alan

0

我脑海中有一个例子,假设您想将类实例数量限制为0或1。 例如,对于某些单例类,您希望应用程序可以临时销毁对象以减少内存使用。为了实现这一点,构造函数将是私有的,但析构函数将是公共的。请参见以下代码片段。

class SingletoneBigMemoryConsumer
{
private:
    SingletoneBigMemoryConsumer()
    {
        // Allocate a lot of resource here.
    }

public:
    static SingletoneBigMemoryConsumer* getInstance()
    { 
        if (instance != NULL) 
            return instance;
        else
            return new SingletoneBigMemoryConsumer();
    }
    ~SingletoneBigMemoryConsumer()
    {
        // release the allocated resource.
        instance = NULL;
    }
private:
    // data memeber.
    static SingletoneBigMemoryConsumer* instance;
}



//Usage.
SingletoneBigMemoryConsumer* obj = SingletoneBigMemoryConsumer::getInstance();
// You cannot create more SingletoneBigMemoryConsumer here.
// After 1 seconds usage, delete it to reduce memory usage.
delete obj;
// You can create an new one when needed later

0
一个对象的所有者需要访问析构函数来销毁它。如果构造函数是私有的,那么必须有一些可访问的函数来创建对象。如果该函数将构造的对象的所有权转移给调用者(例如返回指向自由存储器上对象的指针),则调用者在决定删除对象时必须有权访问析构函数。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接