朋克大暴走MOD菜单最新版
156.69MB · 2025-10-25
在C++中,即使你定义一个空类,编译器也会在背后为你生成一系列函数。理解这些"默默生成"的函数是掌握C++对象模型的关键,也是避免潜在陷阱的基础。
看似简单的空类:
class Empty {};
编译器实际生成的等价代码:
class Empty {
public:
// 1. 默认构造函数
Empty() {}
// 2. 拷贝构造函数
Empty(const Empty& other) {}
// 3. 拷贝赋值运算符
Empty& operator=(const Empty& other) {
return *this;
}
// 4. 析构函数
~Empty() {}
};
实际验证:
void demonstrate_auto_generation() {
Empty e1; // 调用默认构造函数
Empty e2(e1); // 调用拷贝构造函数
e2 = e1; // 调用拷贝赋值运算符
// 离开作用域时调用析构函数
}
C++11后的空类实际获得:
class Empty {
public:
// 经典四巨头
Empty() {}
Empty(const Empty& other) {}
Empty& operator=(const Empty& other) { return *this; }
~Empty() {}
// C++11新增的两个移动操作
Empty(Empty&& other) {} // 移动构造函数
Empty& operator=(Empty&& other) { return *this; } // 移动赋值运算符
};
class Customer {
public:
// 如果用户不声明,编译器生成:
// Customer(const Customer& other)
// : name(other.name), address(other.address), id(other.id) {}
private:
std::string name;
std::string address;
int id;
};
内置类型的陷阱:
class Dangerous {
public:
// 编译器生成的拷贝构造函数:
// Dangerous(const Dangerous& other) : ptr(other.ptr) {}
// 这就是浅拷贝!两个对象共享同一块内存
private:
int* ptr; // 指向动态分配的内存
};
编译器生成的拷贝赋值运算符:
template<typename T>
class NamedObject {
public:
// 编译器可能生成:
// NamedObject& operator=(const NamedObject& other) {
// nameValue = other.nameValue;
// objectValue = other.objectValue;
// return *this;
// }
private:
std::string nameValue;
T objectValue;
};
class Problematic {
public:
Problematic(std::string& str, int val)
: refMember(str), constMember(val) {}
// 编译器不会生成拷贝赋值运算符!
// 因为无法修改引用指向和const成员
private:
std::string& refMember; // 引用成员
const int constMember; // const成员
};
void demonstrate_issue() {
std::string s1 = "hello", s2 = "world";
Problematic p1(s1, 1), p2(s2, 2);
// p1 = p2; // 错误!拷贝赋值运算符被隐式删除
}
class Base {
private:
Base(const Base&); // 私有拷贝构造,不定义
Base& operator=(const Base&); // 私有拷贝赋值,不定义
};
class Derived : public Base {
// 编译器不会为Derived生成拷贝构造和拷贝赋值!
// 因为无法调用基类的对应函数
};
void inheritance_issue() {
Derived d1;
// Derived d2(d1); // 错误!拷贝构造函数被删除
// d2 = d1; // 错误!拷贝赋值运算符被删除
}
// 经典三法则:如果需要定义拷贝控制函数之一,可能需要定义全部三个
class RuleOfThree {
public:
// 构造函数
RuleOfThree(const char* data)
: size_(std::strlen(data)),
data_(new char[size_ + 1]) {
std::strcpy(data_, data);
}
// 1. 用户定义析构函数 - 管理资源
~RuleOfThree() {
delete[] data_;
}
// 2. 用户定义拷贝构造函数 - 深拷贝
RuleOfThree(const RuleOfThree& other)
: size_(other.size_),
data_(new char[other.size_ + 1]) {
std::strcpy(data_, other.data_);
}
// 3. 用户定义拷贝赋值运算符 - 深拷贝和自赋值安全
RuleOfThree& operator=(const RuleOfThree& other) {
if (this != &other) { // 自赋值检查
delete[] data_; // 释放原有资源
size_ = other.size_;
data_ = new char[size_ + 1];
std::strcpy(data_, other.data_);
}
return *this;
}
private:
std::size_t size_;
char* data_;
};
class RuleOfFive {
public:
// 构造函数
RuleOfFive(const char* data)
: size_(std::strlen(data)),
data_(new char[size_ + 1]) {
std::strcpy(data_, data);
}
// 1. 析构函数
~RuleOfFive() {
delete[] data_;
}
// 2. 拷贝构造函数
RuleOfFive(const RuleOfFive& other)
: size_(other.size_),
data_(new char[other.size_ + 1]) {
std::strcpy(data_, other.data_);
}
// 3. 拷贝赋值运算符
RuleOfFive& operator=(const RuleOfFive& other) {
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = new char[size_ + 1];
std::strcpy(data_, other.data_);
}
return *this;
}
// 4. 移动构造函数 - C++11新增
RuleOfFive(RuleOfFive&& other) noexcept
: size_(other.size_), data_(other.data_) {
other.size_ = 0;
other.data_ = nullptr; // 源对象置于有效状态
}
// 5. 移动赋值运算符 - C++11新增
RuleOfFive& operator=(RuleOfFive&& other) noexcept {
if (this != &other) {
delete[] data_;
size_ = other.size_;
data_ = other.data_;
other.size_ = 0;
other.data_ = nullptr;
}
return *this;
}
private:
std::size_t size_;
char* data_;
};
// 零法则:让编译器生成所有函数,通过组合管理资源
class RuleOfZero {
public:
// 不需要用户定义任何拷贝控制函数!
RuleOfZero(const std::string& data) : data_(data) {}
// 编译器自动生成所有六个函数:
// - 默认构造函数(如果没声明其他构造函数)
// - 拷贝构造函数
// - 拷贝赋值运算符
// - 移动构造函数
// - 移动赋值运算符
// - 析构函数
private:
std::string data_; // std::string自己管理资源
};
// 组合智能指针进一步简化资源管理
class ModernResourceHandler {
public:
ModernResourceHandler(const std::string& name)
: name_(name),
data_(std::make_unique<std::vector<int>>()) {}
// 编译器生成的函数完全正确且安全!
// unique_ptr自动处理资源生命周期
private:
std::string name_;
std::unique_ptr<std::vector<int>> data_;
};
class LegacyString {
public:
LegacyString(const char* str = nullptr) {
if (str) {
size_ = std::strlen(str);
data_ = new char[size_ + 1];
std::strcpy(data_, str);
} else {
size_ = 0;
data_ = nullptr;
}
}
// 只有析构函数,违反三法则!
~LegacyString() {
delete[] data_;
}
// 编译器会生成拷贝构造和拷贝赋值,但都是浅拷贝!
// 这会导致双重释放的未定义行为
private:
std::size_t size_;
char* data_;
};
void demonstrate_double_free() {
LegacyString s1("hello");
{
LegacyString s2 = s1; // 浅拷贝,共享数据
} // s2析构,释放内存
// s1现在持有悬空指针!
} // s1再次析构,双重释放!
class ModernString {
public:
ModernString(const char* str = nullptr)
: data_(str ? std::make_unique<char[]>(std::strlen(str) + 1) : nullptr) {
if (str) {
std::strcpy(data_.get(), str);
}
}
// 不需要用户定义任何拷贝控制函数!
// unique_ptr自动禁止拷贝,允许移动
// 编译器生成的行为完全正确
// 显式提供移动操作以改善性能
ModernString(ModernString&&) = default;
ModernString& operator=(ModernString&&) = default;
// 显式删除拷贝操作以明确意图
ModernString(const ModernString&) = delete;
ModernString& operator=(const ModernString&) = delete;
private:
std::unique_ptr<char[]> data_;
};
class GenerationRules {
public:
// 情况1:用户声明了拷贝构造函数
GenerationRules(const GenerationRules&) {}
// 结果:编译器不会生成移动构造函数和移动赋值运算符
// 情况2:用户声明了移动操作
GenerationRules(GenerationRules&&) {}
// 结果:编译器不会生成拷贝操作,但会生成默认构造和析构
// 情况3:用户声明了析构函数
~GenerationRules() {}
// 结果:C++11前:不影响;C++11后:可能抑制移动操作生成
};
// 现代最佳实践:显式控制
class ExplicitControl {
public:
ExplicitControl() = default;
~ExplicitControl() = default;
// 显式使用默认行为
ExplicitControl(const ExplicitControl&) = default;
ExplicitControl& operator=(const ExplicitControl&) = default;
// 显式启用移动
ExplicitControl(ExplicitControl&&) = default;
ExplicitControl& operator=(ExplicitControl&&) = default;
// 或者显式删除
// ExplicitControl(const ExplicitControl&) = delete;
};
class Base {
public:
virtual ~Base() = default;
// 显式启用移动
Base(Base&&) = default;
Base& operator=(Base&&) = default;
};
class Derived : public Base {
public:
// 编译器会为Derived生成移动操作吗?
// 只有基类和所有成员都可移动时才会生成
private:
std::vector<int> data_; // 可移动的成员
};
= default和= delete明确控制生成最终建议: 将编译器生成函数视为一种设计工具而非实现细节。在编写每个类时,都应该有意识地思考:"我需要编译器生成哪些函数?我应该显式控制哪些函数?" 这种主动思考的习惯是成为C++专家的关键标志。
记住:在C++中,了解编译器在背后做什么与了解你自己要写什么代码同样重要。 条款5为我们揭示了C++对象模型的基础机制,是理解更高级特性的基石。