C++ 私有构造函数 (Private Constructor)

黎 浩然/ 6 12 月, 2023/ C/C++, 计算机/COMPUTER/ 0 comments

单例模式 (Singleton Pattern)

这是 private 构造函数最常见和典型的应用场景。单例模式确保一个类在整个应用程序中只存在一个实例,并提供一个全局访问点来获取这个唯一的实例。

为什么使用 private 构造函数:

  • 强制单例: 通过将构造函数设为 private,外部代码无法直接使用 new 或在栈上创建该类的对象。
  • 统一访问点: 通常会提供一个公共的静态方法(例如 getInstance() 或 Instance())来获取或创建唯一的实例。这个静态方法负责管理实例的生命周期(例如懒惰初始化)。

示例:

class Singleton {
private:
    // 私有构造函数,防止外部直接创建对象
    Singleton() {
        // 初始化逻辑
    }

    // 删除拷贝构造函数和赋值运算符,防止复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

public:
    // 获取唯一实例的静态方法
    static Singleton& getInstance() {
        static Singleton instance; // 懒惰初始化,线程安全(C++11之后)
        return instance;
    }

    void doSomething() {
        // ...
    }
};

// 使用
// Singleton s; // 错误:构造函数是私有的
// Singleton& s1 = Singleton::getInstance();
// Singleton& s2 = Singleton::getInstance();
// assert(&s1 == &s2); // 总是同一个实例

工厂方法模式 (Factory Method Pattern)

当对象的创建过程比较复杂,或者你希望将对象的创建逻辑从客户端代码中分离出来时,可以使用工厂方法。private 构造函数可以确保对象只能通过工厂方法来创建。

为什么使用 private 构造函数:

  • 控制创建逻辑: 强制所有对象都通过工厂方法创建,这样可以在创建对象前执行一些复杂的初始化、参数验证、资源管理、或者根据不同条件创建不同类型的对象。
  • 封装内部实现: 客户端不需要知道具体对象的构造细节,只需要调用工厂方法即可。
  • 返回抽象类型: 工厂方法可以返回基类指针或引用,从而实现多态性,隐藏具体实现类。

示例:

class Product {
public:
    virtual void use() = 0;
    virtual ~Product() = default;
};

class ConcreteProductA : public Product {
private:
    // 私有构造函数
    ConcreteProductA() { /* ... */ }
    friend class ProductFactory; // 允许工厂类访问私有构造函数
public:
    void use() override { /* ... */ }
};

class ConcreteProductB : public Product {
private:
    // 私有构造函数
    ConcreteProductB() { /* ... */ }
    friend class ProductFactory; // 允许工厂类访问私有构造函数
public:
    void use() override { /* ... */ }
};

class ProductFactory {
public:
    static Product* createProduct(int type) {
        if (type == 1) {
            return new ConcreteProductA();
        } else if (type == 2) {
            return new ConcreteProductB();
        }
        return nullptr;
    }
};

// 使用
// ProductA pA; // 错误:构造函数是私有的
// Product* p1 = ProductFactory::createProduct(1);
// Product* p2 = ProductFactory::createProduct(2);
// p1->use();
// p2->use();
// delete p1;
// delete p2;

注意: 在工厂方法模式中,通常会将工厂类声明为友元 (friend) 类,以便它可以访问私有构造函数来创建对象。

禁止在栈上创建对象

如果你希望某个类的对象只能通过 new 在堆上创建(通常是为了管理复杂的资源或确保对象的生命周期可控),可以将构造函数设为 private。

为什么使用 private 构造函数:

  • 强制堆分配: private 构造函数禁止在栈上直接创建对象,因为栈上对象的创建需要直接调用构造函数。
  • 通过工厂方法返回指针: 通常会提供一个公共的静态方法,它内部使用 new 来创建对象并返回指针。

示例:

class ComplexResource {
private:
    ComplexResource() { /* 复杂的资源初始化 */ }
    // 禁止在栈上创建
    ComplexResource(const ComplexResource&) = delete;
    ComplexResource& operator=(const ComplexResource&) = delete;

public:
    static ComplexResource* createResource() {
        return new ComplexResource();
    }

    void releaseResource() { /* 资源释放 */ }
    // ...
};

// 使用
// ComplexResource res; // 错误:构造函数是私有的
// ComplexResource* pRes = ComplexResource::createResource();
// pRes->releaseResource();
// delete pRes;

工具类 (Utility Class)

如果一个类只包含静态成员函数和静态成员变量,你可能不希望它的实例被创建。虽然可以直接不提供公共构造函数,但显式地将其设为 private 可以更清楚地表达意图。

为什么使用 private 构造函数:

  • 禁止实例化: 明确表示该类不应该被实例化,它只是一个逻辑上的容器。
  • 所有功能都是静态的: 强调该类的所有功能都通过静态方法提供。

示例:

class MathUtils {
private:
    // 私有构造函数,防止实例化
    MathUtils() = delete; 

public:
    static int add(int a, int b) {
        return a + b;
    }

    static int subtract(int a, int b) {
        return a - b;
    }
};

// 使用
// MathUtils mu; // 错误:构造函数已删除
int sum = MathUtils::add(5, 3);

防止复制和赋值(配合删除的构造函数)

虽然通常通过 delete 拷贝构造函数和拷贝赋值运算符来防止复制,但如果将默认构造函数也设为 private,可以更好地控制对象的创建。

示例: 这通常与单例模式或工厂方法模式结合使用,如上述单例模式的例子所示。

总结

将 C++ 构造函数声明为 private 的核心目的是限制或控制类的实例化。它强制客户端代码通过特定的途径(如静态工厂方法)来获取对象,从而实现以下目标:

  • 控制对象的生命周期和数量(如单例模式)。
  • 封装对象的创建逻辑(如工厂方法模式)。
  • 强制特定的内存分配方式(如只能在堆上创建)。
  • 表达类只作为静态工具的意图
Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*