Ch 20: 构造函数和析构函数
- 掌握各种构造函数的写法和适用场景
- 深入理解析构函数的作用和 RAII 模式
- 学会正确使用初始化列表
- 理解委托构造和移动构造
- 掌握 Rule of 5 和 Rule of 0
- 理解 default 和 delete 的用法
20.1 Python init 和 del 对比
Section titled “20.1 Python init 和 del 对比”Python 初始化和清理
Section titled “Python 初始化和清理”class Resource: def __init__(self, name): self.name = name print(f"Creating {name}")
def __del__(self): # Python 垃圾回收,不保证及时调用 print(f"Destroying {self.name}")
def process(self): print(f"Processing {self.name}")
# Python 的初始化r = Resource("File") # Creating Filer.process()del r # 显式删除会调用 __del__C++ 构造和析构
Section titled “C++ 构造和析构”#include <string>#include <iostream>
class Resource {public: Resource(const std::string& name) : name_(name) { std::cout << "Creating " << name_ << "\n"; }
// 析构函数 - 对象销毁时自动调用 ~Resource() { std::cout << "Destroying " << name_ << "\n"; }
void process() { std::cout << "Processing " << name_ << "\n"; }
private: std::string name_;};
int main() { Resource r("File"); // Creating File r.process(); // Processing File // 析构函数在 r 离开作用域时自动调用 return 0;}| 特性 | Python | C++ |
|---|---|---|
| 初始化 | __init__ | 构造函数 |
| 清理 | __del__(不确定) | 析构函数(确定) |
| 生命周期 | 垃圾回收 | 确定性(RAII) |
| 调用时机 | 不确定 | 离开作用域时 |
20.2 构造函数详解
Section titled “20.2 构造函数详解”默认构造函数
Section titled “默认构造函数”#include <iostream>#include <string>
class Widget {public: // 方式 1:用户声明的默认构造函数 Widget() : value_(0) { std::cout << "Widget()\n"; }
// 方式 2:使用 = default(C++11,推荐) Widget() = default;
int value() const { return value_; }
private: int value_;};
int main() { Widget w1; // 调用默认构造函数 Widget w2{}; // 同样 Widget w3 = Widget(); // 同样
std::cout << w1.value() << "\n";}参数化构造函数
Section titled “参数化构造函数”#include <string>#include <iostream>
class Person {public: // 单参数构造(可以用 = 直接初始化) explicit Person(const std::string& name) : name_(name), age_(0) {}
// 多参数构造 Person(const std::string& name, int age) : name_(name), age_(age) {}
// 带有默认参数的构造函数 Person(const std::string& name = "Unknown", int age = 0) : name_(name), age_(age) {}
std::string info() const { return name_ + ", " + std::to_string(age_); }
private: std::string name_; int age_;};
int main() { Person p1; // 使用默认值 Person p2("Alice"); // Alice, 0 Person p3("Bob", 30); // Bob, 30 Person p4{"Charlie", 25}; // Charlie, 25
std::cout << p1.info() << "\n"; std::cout << p2.info() << "\n"; std::cout << p3.info() << "\n";}委托构造函数(C++11)
Section titled “委托构造函数(C++11)”#include <string>#include <iostream>
class Rectangle {private: double width_; double height_;
public: // 委托到四参数构造函数 Rectangle() : Rectangle(0, 0) {}
// 委托到四参数构造函数 Rectangle(double size) : Rectangle(size, size) {}
// 主构造函数 Rectangle(double w, double h) : width_(w), height_(h) {}
double area() const { return width_ * height_; }};
class Logger {private: std::string name_; int level_;
public: // 委托到另一个构造函数 Logger() : Logger("default", 0) {}
Logger(const std::string& name) : Logger(name, 0) {}
Logger(const std::string& name, int level) : name_(name), level_(level) {}
void log(const std::string& msg) const { std::cout << "[" << level_ << "] " << name_ << ": " << msg << "\n"; }};
int main() { Rectangle r1; // 0 x 0 Rectangle r2(5); // 5 x 5 Rectangle r3(3, 4); // 3 x 4
std::cout << "Areas: " << r1.area() << ", " << r2.area() << ", " << r3.area() << "\n";
Logger l1; Logger l2("App"); Logger l3("App", 3);
l1.log("Started"); l2.log("Running"); l3.log("Warning");}拷贝构造函数
Section titled “拷贝构造函数”#include <string>#include <iostream>
class Person {public: Person(const std::string& name, int age) : name_(name), age_(age) {}
// 拷贝构造函数 - 用另一个对象初始化 Person(const Person& other) : name_(other.name_), age_(other.age_) { std::cout << "Copying " << name_ << "\n"; }
std::string info() const { return name_ + ", " + std::to_string(age_); }
private: std::string name_; int age_;};
int main() { Person p1("Alice", 30);
// 调用拷贝构造函数 Person p2(p1); // Copying Alice Person p3 = p1; // Copying Alice
// 函数参数传递 void process(Person p); process(p1); // 拷贝 Alice
// 函数返回(可能触发拷贝) Person create_person() { Person temp("Bob", 25); return temp; // 可能触发拷贝或移动 }
return 0;}移动构造函数(C++11)
Section titled “移动构造函数(C++11)”#include <string>#include <iostream>
class Person {private: std::string name_; int* data_;
public: Person(const std::string& name, int size) : name_(name), data_(new int[size]) { std::cout << "Constructing " << name_ << "\n"; }
// 拷贝构造函数 Person(const Person& other) : name_(other.name_), data_(new int[100]) { std::cout << "Copying " << name_ << "\n"; // 深拷贝 std::copy(other.data_, other.data_ + 100, data_); }
// 移动构造函数 - 转移资源所有权 Person(Person&& other) noexcept : name_(std::move(other.name_)), data_(other.data_) { std::cout << "Moving " << name_ << "\n"; other.data_ = nullptr; // 防止析构函数释放 }
// 移动赋值运算符 Person& operator=(Person&& other) noexcept { if (this != &other) { delete[] data_; name_ = std::move(other.name_); data_ = other.data_; other.data_ = nullptr; } std::cout << "Move assigning " << name_ << "\n"; return *this; }
~Person() { std::cout << "Destroying " << name_ << "\n"; delete[] data_; }};
int main() { Person p1("Alice", 100); Person p2(std::move(p1)); // 移动,p1 变为空壳
std::cout << "p2's name: " << p2.info() << "\n"; // p1 现在处于有效但未定义状态,data_ 为 nullptr
return 0;}20.3 初始化列表详解
Section titled “20.3 初始化列表详解”为什么用初始化列表
Section titled “为什么用初始化列表”#include <string>#include <iostream>
class Person {public: // ✅ 推荐:初始化列表 Person(const std::string& name, int age) : name_(name), age_(age) {} // 直接初始化
// ❌ 不推荐:赋值方式 Person(const std::string& name, int age) { name_ = name; // 先默认构造,再赋值 age_ = age; // 多余开销 }
private: std::string name_; // 默认构造 int age_; // 默认构造};#include <iostream>
class Order {public: Order(int priority) : id_(next_id_++), priority_(priority) { std::cout << "Order " << id_ << " created\n"; }
private: int id_; int priority_; static int next_id_;};
int Order::next_id_ = 1;
int main() { Order o1(1); // id = 1 Order o2(2); // id = 2 Order o3(3); // id = 3
return 0;}成员初始化的其他方式
Section titled “成员初始化的其他方式”#include <string>
class Widget {private: // 方式 1:声明时默认成员初始化(C++11) int value_ = 0; std::string name_ = "default";
// 方式 2:使用 default double factor_ = 1.0;
// 方式 3:使用花括号 bool enabled_{true};
public: // 构造函数中的初始化列表会覆盖默认值 Widget() = default;};20.4 析构函数详解
Section titled “20.4 析构函数详解”析构函数基础
Section titled “析构函数基础”#include <fstream>#include <string>#include <iostream>
class FileHandler {public: explicit FileHandler(const std::string& filename) : filename_(filename) { file_.open(filename, std::ios::out); if (file_.is_open()) { std::cout << "File opened: " << filename_ << "\n"; } }
// 析构函数 - 对象销毁时自动调用 ~FileHandler() { if (file_.is_open()) { file_.close(); std::cout << "File closed: " << filename_ << "\n"; } }
void write(const std::string& data) { if (file_.is_open()) { file_ << data; } }
private: std::fstream file_; std::string filename_;};
int main() { { FileHandler fh("test.txt"); fh.write("Hello, World!"); // fh 离开作用域,析构函数被调用 } std::cout << "FileHandler destroyed\n";
return 0;}RAII 模式(资源获取即初始化)
Section titled “RAII 模式(资源获取即初始化)”#include <mutex>#include <iostream>
// RAII 模式确保资源在离开作用域时被释放class Lock {private: std::mutex& mutex_; bool locked_ = false;
public: explicit Lock(std::mutex& m) : mutex_(m) { mutex_.lock(); locked_ = true; std::cout << "Locked\n"; }
~Lock() { if (locked_) { mutex_.unlock(); std::cout << "Unlocked\n"; } }};
std::mutex shared_mutex;
void process() { Lock lock(shared_mutex); // 获取锁 // ... 执行工作 ...} // 离开作用域,自动释放锁
int main() { std::cout << "Starting\n"; process(); std::cout << "Done\n";
return 0;}栈展开和异常安全
Section titled “栈展开和异常安全”#include <iostream>#include <stdexcept>
class Cleanup {public: ~Cleanup() { std::cout << "Cleanup performed\n"; }};
void risky_function(bool should_throw) { Cleanup cleanup; // 确保清理
std::cout << "Doing work\n";
if (should_throw) { throw std::runtime_error("An error occurred!"); }
std::cout << "Work completed\n";}
int main() { try { risky_function(false); // 不抛异常 } catch (...) { std::cout << "Exception caught\n"; }
try { risky_function(true); // 抛异常 } catch (...) { std::cout << "Exception caught\n"; }
std::cout << "Done\n";
return 0;}20.5 Rule of 5 和 Rule of 0
Section titled “20.5 Rule of 5 和 Rule of 0”特殊成员函数
Section titled “特殊成员函数”C++ 类有 5 个特殊成员函数:
- 析构函数
~Class() - 拷贝构造函数
Class(const Class&) - 拷贝赋值运算符
Class& operator=(const Class&) - 移动构造函数
Class(Class&&) - 移动赋值运算符
Class& operator=(Class&&)
Rule of 5
Section titled “Rule of 5”如果需要自定义任意一个,通常需要全部定义:
#include <string>#include <iostream>#include <utility>
class RuleOf5 {private: std::string data_; int* buffer_;
public: explicit RuleOf5(const std::string& d, int size) : data_(d), buffer_(new int[size]) { std::cout << "Constructing RuleOf5\n"; }
// 析构函数 ~RuleOf5() { std::cout << "Destructing RuleOf5\n"; delete[] buffer_; }
// 拷贝构造函数 RuleOf5(const RuleOf5& other) : data_(other.data_), buffer_(new int[100]) { std::cout << "Copying RuleOf5\n"; std::copy(other.buffer_, other.buffer_ + 100, buffer_); }
// 拷贝赋值运算符 RuleOf5& operator=(const RuleOf5& other) { if (this != &other) { std::cout << "Copy assigning RuleOf5\n"; data_ = other.data_; delete[] buffer_; buffer_ = new int[100]; std::copy(other.buffer_, other.buffer_ + 100, buffer_); } return *this; }
// 移动构造函数 RuleOf5(RuleOf5&& other) noexcept : data_(std::move(other.data_)), buffer_(other.buffer_) { std::cout << "Moving RuleOf5\n"; other.buffer_ = nullptr; // 防止双重删除 }
// 移动赋值运算符 RuleOf5& operator=(RuleOf5&& other) noexcept { if (this != &other) { std::cout << "Move assigning RuleOf5\n"; delete[] buffer_; data_ = std::move(other.data_); buffer_ = other.buffer_; other.buffer_ = nullptr; } return *this; }};
int main() { RuleOf5 a("Hello", 100);
std::cout << "--- Copy ---\n"; RuleOf5 b(a); // 拷贝构造
std::cout << "--- Move ---\n"; RuleOf5 c(std::move(a)); // 移动构造
std::cout << "--- Copy assign ---\n"; b = c; // 拷贝赋值
std::cout << "--- Move assign ---\n"; b = std::move(c); // 移动赋值
std::cout << "--- End ---\n";
return 0;}Rule of 0
Section titled “Rule of 0”如果类不需要管理资源(使用 RAII 包装器),让编译器生成默认版本:
#include <string>#include <iostream>
// Rule of 0 - 不需要定义任何特殊成员函数class Simple {public: Simple(int v) : value_(v) {} int value() const { return value_; }
private: int value_;};
// 编译器自动生成所有 5 个特殊成员函数// 所有都是 default 的行为
// 另一个 Rule of 0 的例子class Point {public: Point(double x, double y) : x_(x), y_(y) {}
double x() const { return x_; } double y() const { return y_; }
private: double x_; double y_;};
// std::string 本身已经正确处理资源class Person {public: Person(const std::string& name) : name_(name) {}
const std::string& name() const { return name_; }
private: std::string name_;};// 不需要自定义拷贝/移动,std::string 会正确处理
int main() { Simple s1(1); Simple s2(s1); // 默认拷贝
Person p1("Alice"); Person p2(p1); // 默认拷贝
std::cout << "Rule of 0 works\n";
return 0;}20.6 default 和 delete
Section titled “20.6 default 和 delete”= default 显式默认
Section titled “= default 显式默认”#include <string>#include <iostream>
class Widget {public: // 要求编译器生成默认版本 Widget() = default;
// 显式默认的拷贝构造 Widget(const Widget&) = default;
// 显式默认的移动构造 Widget(Widget&&) = default;
// 显式默认的析构函数(virtual) virtual ~Widget() = default;
// 显式默认的拷贝赋值 Widget& operator=(const Widget&) = default;
// 显式默认的移动赋值 Widget& operator=(Widget&&) = default;};
struct Base { virtual ~Base() = default;};
struct Derived : Base { ~Derived() override = default;};= delete 禁用函数
Section titled “= delete 禁用函数”#include <string>#include <iostream>
// 禁止拷贝的类class NonCopyable {public: NonCopyable() = default;
// 显式删除拷贝构造函数 NonCopyable(const NonCopyable&) = delete;
// 显式删除拷贝赋值运算符 NonCopyable& operator=(const NonCopyable&) = delete;
// 允许移动 NonCopyable(NonCopyable&&) = default; NonCopyable& operator=(NonCopyable&&) = default;};
int main() { NonCopyable nc1; // NonCopyable nc2 = nc1; // ❌ 编译错误!拷贝被禁用
NonCopyable nc3 = std::move(nc1); // OK,移动允许
return 0;}delete 的其他用途
Section titled “delete 的其他用途”#include <iostream>
// 禁止某些重载class Integer {public: Integer(int value) : value_(value) {}
// 禁止从 double 构造(避免精度丢失) explicit Integer(double) = delete;
// 禁止从 bool 构造(避免意外) explicit Integer(bool) = delete;
int value() const { return value_; }
private: int value_;};
// 只允许某些值class PositiveInt {public: explicit PositiveInt(int value) : value_(value) {}
// 禁止负数 static PositiveInt create(int value) { if (value < 0) { throw std::invalid_argument("Negative not allowed"); } return PositiveInt(value); }
int value() const { return value_; }
private: int value_;};
// 禁止某些函数调用void process(int* ptr) { // 处理指针}
// 禁止传递 null 指针的重载void process(std::nullptr_t) = delete;
int main() { Integer i1(42); // OK // Integer i2(3.14); // ❌ 编译错误!double 构造函数被删除 // Integer i3(true); // ❌ 编译错误!bool 构造函数被删除
// process(nullptr); // ❌ 编译错误!
std::cout << i1.value() << "\n";
return 0;}20.7 完整示例:智能指针模拟
Section titled “20.7 完整示例:智能指针模拟”#include <iostream>#include <stdexcept>
// 简化版 unique_ptrtemplate<typename T>class UniquePtr {private: T* ptr_ = nullptr;
public: // 默认构造 UniquePtr() = default;
// 构造函数,接受原始指针 explicit UniquePtr(T* raw_ptr) : ptr_(raw_ptr) {}
// 禁用拷贝 UniquePtr(const UniquePtr&) = delete; UniquePtr& operator=(const UniquePtr&) = delete;
// 移动构造函数 UniquePtr(UniquePtr&& other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }
// 移动赋值 UniquePtr& operator=(UniquePtr&& other) noexcept { if (this != &other) { delete ptr_; ptr_ = other.ptr_; other.ptr_ = nullptr; } return *this; }
// 解引用 T& operator*() const { if (!ptr_) throw std::runtime_error("Dereferencing null pointer"); return *ptr_; }
// 箭头运算符 T* operator->() const { if (!ptr_) throw std::runtime_error("Accessing null pointer"); return ptr_; }
// 获取原始指针 T* get() const { return ptr_; }
// 释放所有权 T* release() { T* temp = ptr_; ptr_ = nullptr; return temp; }
// 析构函数 ~UniquePtr() { delete ptr_; }};
struct Resource { std::string name; Resource(const std::string& n) : name(n) { std::cout << "Acquiring " << name << "\n"; } ~Resource() { std::cout << "Releasing " << name << "\n"; }};
int main() { { UniquePtr<Resource> r1(new Resource("File")); std::cout << "r1 owns: " << r1->name << "\n";
// 移动 UniquePtr<Resource> r2 = std::move(r1); // std::cout << r1->name << "\n"; // ❌ r1 现在是空 std::cout << "r2 owns: " << r2->name << "\n";
} // Resource 在这里被释放
std::cout << "Done\n";
return 0;}20.8 章节总结
Section titled “20.8 章节总结”| 特殊成员函数 | 作用 | 默认行为 |
|---|---|---|
析构函数 ~Class() | 对象销毁时清理 | 默认析构非virtual |
拷贝构造 Class(const Class&) | 用同类对象初始化 | 浅拷贝 |
拷贝赋值 operator=(const Class&) | 赋值操作 | 浅拷贝 |
移动构造 Class(Class&&) | 转移所有权 | 转移资源 |
移动赋值 operator=(Class&&) | 转移所有权 | 转移资源 |
选择原则:
- 管理资源(指针、文件句柄等)→ 自定义全部 5 个(Rule of 5)
- 不管理资源 → 使用默认(Rule of 0)
= default显式要求默认实现= delete禁用不需要的函数
下章预告:ch21 学习继承和多态,理解基类、派生类和虚函数。