C++ 私有构造函数 (Private Constructor)
单例模式 (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 的核心目的是限制或控制类的实例化。它强制客户端代码通过特定的途径(如静态工厂方法)来获取对象,从而实现以下目标:
- 控制对象的生命周期和数量(如单例模式)。
- 封装对象的创建逻辑(如工厂方法模式)。
- 强制特定的内存分配方式(如只能在堆上创建)。
- 表达类只作为静态工具的意图。