在实际开发中,某些类的实例如果允许拷贝,会导致逻辑混乱或资源管理问题。典型的例子包括:
在C++98标准中,我们通过将拷贝构造函数和赋值运算符声明为private并不提供实现来禁止拷贝:
cpp复制class NonCopyable {
public:
NonCopyable() = default;
private:
NonCopyable(const NonCopyable&); // 只声明不实现
NonCopyable& operator=(const NonCopyable&); // 只声明不实现
};
这种设计的关键点在于:
注意:现代编译器中,即使不写拷贝控制成员,编译器也可能自动生成移动操作。如果需要完全禁止拷贝语义,最好也显式删除移动操作。
C++11引入了=delete语法,可以更直观地表达设计意图:
cpp复制class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
这种方式的优势:
需要严格控制对象生命周期的场景:
最直接的实现方式是私有化构造函数,提供静态创建接口:
cpp复制class HeapOnly {
public:
static HeapOnly* Create() {
return new HeapOnly();
}
void Destroy() {
delete this;
}
private:
HeapOnly() = default;
~HeapOnly() = default;
// 禁止拷贝和移动
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
};
关键设计点:
另一种巧妙的实现方式是利用析构函数的访问控制:
cpp复制class HeapOnly {
public:
HeapOnly() = default;
void Destroy() {
delete this;
}
private:
~HeapOnly() = default;
// 禁止拷贝和移动
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
};
这种方案的原理:
cpp复制std::shared_ptr<HeapOnly> CreateShared() {
return std::shared_ptr<HeapOnly>(HeapOnly::Create(),
[](HeapOnly* p) { p->Destroy(); });
}
最可靠的实现方式是禁用所有堆内存操作:
cpp复制class StackOnly {
public:
StackOnly() = default;
~StackOnly() = default;
// 禁用所有堆内存操作
void* operator new(size_t) = delete;
void operator delete(void*) = delete;
void* operator new[](size_t) = delete;
void operator delete[](void*) = delete;
// 可以允许拷贝(可选)
StackOnly(const StackOnly&) = default;
StackOnly& operator=(const StackOnly&) = default;
};
这种设计确保:
问题1:如何防止对象被间接放置在堆上?
解决方案:除了禁用new,还需要防止对象被包含在可能堆分配的类型中:
cpp复制class HeapContainer {
StackOnly member; // 这样是可以的
std::vector<StackOnly> elements; // 这样会编译失败
};
问题2:如何实现移动语义?
解决方案:可以正常实现移动操作,因为移动不涉及堆分配:
cpp复制class StackOnly {
public:
StackOnly(StackOnly&&) = default;
StackOnly& operator=(StackOnly&&) = default;
};
问题3:如何与STL算法配合使用?
解决方案:只要不涉及堆分配,STL算法可以正常使用:
cpp复制void ProcessStackObjects() {
StackOnly arr[10]; // 栈上数组
std::sort(std::begin(arr), std::end(arr)); // 可以正常工作
}
通过私有构造函数和静态工厂方法实现:
cpp复制class FinalClass {
public:
static FinalClass Create() {
return FinalClass();
}
void DoSomething() {
// 实现逻辑
}
private:
FinalClass() = default;
// 禁止拷贝(可选)
FinalClass(const FinalClass&) = delete;
FinalClass& operator=(const FinalClass&) = delete;
};
这种方案的局限性:
C++11引入了更直接的语法:
cpp复制class FinalClass final {
public:
FinalClass() = default;
~FinalClass() = default;
void DoSomething() {
// 实现逻辑
}
};
final关键字的优势:
cpp复制namespace Utility {
void Function1();
void Function2();
}
在实际项目中,我们可能需要组合多种特殊属性:
cpp复制// 只能在堆上创建且不可拷贝的类
class HeapOnlyNonCopyable {
public:
static HeapOnlyNonCopyable* Create() {
return new HeapOnlyNonCopyable();
}
void Destroy() {
delete this;
}
private:
HeapOnlyNonCopyable() = default;
~HeapOnlyNonCopyable() = default;
// 禁止拷贝和移动
HeapOnlyNonCopyable(const HeapOnlyNonCopyable&) = delete;
HeapOnlyNonCopyable& operator=(const HeapOnlyNonCopyable&) = delete;
HeapOnlyNonCopyable(HeapOnlyNonCopyable&&) = delete;
HeapOnlyNonCopyable& operator=(HeapOnlyNonCopyable&&) = delete;
};
C++17之后,我们可以利用更现代的特性:
cpp复制// 使用nodiscard避免忘记接收工厂创建的对象
class ModernHeapOnly {
public:
[[nodiscard]] static std::unique_ptr<ModernHeapOnly> Create() {
return std::unique_ptr<ModernHeapOnly>(new ModernHeapOnly());
}
// 使用=delete明确禁止不需要的操作
ModernHeapOnly(const ModernHeapOnly&) = delete;
ModernHeapOnly& operator=(const ModernHeapOnly&) = delete;
// 允许移动语义
ModernHeapOnly(ModernHeapOnly&&) = default;
ModernHeapOnly& operator=(ModernHeapOnly&&) = default;
private:
ModernHeapOnly() = default;
~ModernHeapOnly() = default;
};
cpp复制class Singleton {
public:
static Singleton& Instance() {
static Singleton instance;
return instance;
}
void DoSomething() { /*...*/ }
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
在实际项目中,我经常遇到需要严格限制类使用方式的场景。比如在游戏引擎开发中,资源管理类通常需要是堆专属且不可拷贝的;而在高性能计算模块中,关键数据结构往往是栈专属的。理解这些特殊类的设计方法,可以帮助我们写出更安全、更高效的代码。