目录

第十章 对象生命周期

理解C++对象的生命周期对于编写高效、安全的代码至关重要。对象的生命周期包括创建、使用和销毁三个阶段,每个阶段都有特定的规则和机制。本章将详细介绍对象的构造、析构、复制、移动以及生命周期的管理。

10.1 对象的创建与销毁

10.1.1 构造函数详解

构造函数是对象创建时自动调用的特殊成员函数,用于初始化对象的状态。

默认构造函数

#include <iostream>
using namespace std;
 
class Person {
private:
    string name;
    int age;
 
public:
    // 默认构造函数(无参)
    Person() {
        name = "Unknown";
        age = 0;
        cout << "默认构造函数被调用" << endl;
    }
 
    void display() const {
        cout << name << ", " << age << "岁" << endl;
    }
};
 
int main() {
    Person p1;      // 调用默认构造函数
    Person p2();    // 注意:这是函数声明,不是创建对象!
    Person p3{};    // C++11统一初始化,调用默认构造函数
 
    p1.display();
    return 0;
}

带参数的构造函数

class Person {
private:
    string name;
    int age;
 
public:
    Person(const string &n, int a) {
        name = n;
        age = a;
        cout << "带参数构造函数: " << name << endl;
    }
 
    // 使用初始化列表(更高效)
    Person(const string &n, int a, bool useInitList) 
        : name(n), age(a) {
        cout << "初始化列表构造: " << name << endl;
    }
};

委托构造函数(C++11)

class Person {
private:
    string name;
    int age;
    string address;
 
public:
    // 主构造函数
    Person(const string &n, int a, const string &addr)
        : name(n), age(a), address(addr) {
        cout << "主构造函数" << endl;
    }
 
    // 委托构造函数
    Person(const string &n, int a) : Person(n, a, "Unknown") {
        cout << "委托构造函数(2参数)" << endl;
    }
 
    Person() : Person("Unknown", 0) {
        cout << "委托构造函数(无参)" << endl;
    }
};

10.1.2 析构函数详解

析构函数在对象销毁时自动调用,用于清理资源。

#include <iostream>
#include <fstream>
using namespace std;
 
class FileLogger {
private:
    ofstream file;
    string name;
 
public:
    FileLogger(const string &filename) 
        : name(filename), file(filename) {
        cout << "打开日志文件: " << filename << endl;
        file << "=== 日志开始 ===" << endl;
    }
 
    void log(const string &message) {
        file << message << endl;
    }
 
    // 析构函数
    ~FileLogger() {
        file << "=== 日志结束 ===" << endl;
        cout << "关闭日志文件: " << name << endl;
        // file会自动关闭
    }
};
 
void useLogger() {
    FileLogger logger("app.log");
    logger.log("程序启动");
    logger.log("执行操作");
    // 函数结束时,logger的析构函数自动调用
}
 
int main() {
    useLogger();
    cout << "logger已销毁" << endl;
    return 0;
}

10.2 成员初始化

10.2.1 初始化列表

初始化列表是初始化成员变量的首选方式,特别是对于const成员和引用成员。

#include <iostream>
using namespace std;
 
class Example {
private:
    const int constValue;      // const成员必须在初始化列表中初始化
    int &ref;                  // 引用成员必须在初始化列表中初始化
    int normalValue;
    string name;
 
public:
    // 使用初始化列表
    Example(int cv, int &r, int nv, const string &n)
        : constValue(cv),      // 初始化const成员
          ref(r),              // 初始化引用成员
          normalValue(nv),     // 初始化普通成员
          name(n) {            // 初始化string
        cout << "构造函数体执行" << endl;
        // 这里不再是初始化,而是赋值
    }
};
 
class Member {
public:
    Member() { cout << "Member默认构造" << endl; }
    Member(int) { cout << "Member(int)构造" << endl; }
};
 
class Container {
    Member m1;
    Member m2;
public:
    // 在初始化列表中指定使用的构造函数
    Container() : m1(), m2(10) {
        cout << "Container构造" << endl;
    }
};

初始化列表 vs 构造函数体内赋值

特性 初始化列表 构造函数体内赋值
————————————
const成员 可以初始化 不能初始化
引用成员 可以初始化 不能初始化
效率 直接构造 默认构造+赋值
成员类构造函数选择 可以指定 只能默认构造

10.2.2 类内成员初始化(C++11)

#include <iostream>
#include <vector>
using namespace std;
 
class ModernClass {
private:
    int x = 10;                    // 类内初始值
    string name = "default";       // 类内初始值
    vector<int> data = {1, 2, 3};  // 类内初始值
    static int count;              // 静态成员
 
public:
    ModernClass() = default;       // 显式默认构造函数
 
    ModernClass(int xVal) : x(xVal) {
        // x使用传入的值,name和data使用类内初始值
    }
 
    void print() const {
        cout << "x=" << x << ", name=" << name << endl;
        cout << "data: ";
        for (int v : data) cout << v << " ";
        cout << endl;
    }
};
 
int ModernClass::count = 0;  // 静态成员定义
 
int main() {
    ModernClass obj1;      // 使用类内初始值
    ModernClass obj2(100); // x=100,其他使用类内初始值
 
    obj1.print();
    obj2.print();
 
    return 0;
}

10.3 拷贝控制

10.3.1 拷贝构造函数

拷贝构造函数用于创建一个新对象作为现有对象的副本。

#include <iostream>
#include <cstring>
using namespace std;
 
class String {
private:
    char *data;
    int len;
 
public:
    // 普通构造函数
    String(const char *str = "") {
        len = strlen(str);
        data = new char[len + 1];
        strcpy(data, str);
        cout << "构造函数: " << data << endl;
    }
 
    // 拷贝构造函数
    String(const String &other) {
        len = other.len;
        data = new char[len + 1];
        strcpy(data, other.data);
        cout << "拷贝构造函数: " << data << endl;
    }
 
    // 析构函数
    ~String() {
        cout << "析构函数: " << (data ? data : "null") << endl;
        delete[] data;
    }
 
    void print() const {
        cout << data << endl;
    }
};
 
// 拷贝构造函数被调用的情况
void demo() {
    String s1("Hello");
 
    // 情况1: 使用另一个对象初始化
    String s2 = s1;  // 拷贝构造
 
    // 情况2: 作为参数传递给值传递的函数
    void func(String s);  // 调用时会拷贝
 
    // 情况3: 作为返回值(可能被优化)
    String create() { return String("World"); }
}
 
// 禁用拷贝
class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable &) = delete;  // 禁止拷贝
    NonCopyable &operator=(const NonCopyable &) = delete;  // 禁止赋值
};

10.3.2 拷贝赋值运算符

class String {
    // ... 前面的代码 ...
 
public:
    // 拷贝赋值运算符
    String &operator=(const String &other) {
        cout << "拷贝赋值运算符" << endl;
 
        // 1. 检查自赋值
        if (this != &other) {
            // 2. 分配新资源
            char *newData = new char[other.len + 1];
            strcpy(newData, other.data);
 
            // 3. 释放旧资源
            delete[] data;
 
            // 4. 指向新资源
            data = newData;
            len = other.len;
        }
 
        return *this;
    }
 
    // 更安全的写法:拷贝并交换惯用法
    String &operator=(String other) {
        cout << "拷贝并交换赋值运算符" << endl;
        swap(data, other.data);
        swap(len, other.len);
        return *this;
    }
};

10.4 移动语义(C++11)

10.4.1 为什么需要移动语义

移动语义允许将资源从一个对象“移动”到另一个对象,而不是复制,从而提高性能。

#include <iostream>
#include <vector>
using namespace std;
 
class HugeData {
    vector<int> *data;
 
public:
    HugeData() {
        data = new vector<int>(1000000);
        cout << "构造函数" << endl;
    }
 
    // 拷贝构造函数 - 深拷贝,代价高
    HugeData(const HugeData &other) {
        data = new vector<int>(*other.data);
        cout << "拷贝构造函数(深拷贝)" << endl;
    }
 
    // 移动构造函数 - 转移所有权,代价低
    HugeData(HugeData &&other) noexcept {
        data = other.data;      // 窃取资源
        other.data = nullptr;   // 将源对象置为空
        cout << "移动构造函数(转移)" << endl;
    }
 
    ~HugeData() {
        delete data;
        cout << "析构函数" << endl;
    }
};
 
HugeData createData() {
    HugeData temp;
    return temp;  // 这里会调用移动构造函数(C++11起)
}
 
int main() {
    cout << "=== 拷贝 ===" << endl;
    HugeData d1;
    HugeData d2 = d1;  // 拷贝构造
 
    cout << "\n=== 移动 ===" << endl;
    HugeData d3 = createData();  // 移动构造
 
    cout << "\n=== 显式移动 ===" << endl;
    HugeData d4 = std::move(d3);  // 显式移动
 
    return 0;
}

10.4.2 移动赋值运算符

class HugeData {
    vector<int> *data;
 
public:
    // ... 其他成员函数 ...
 
    // 移动赋值运算符
    HugeData &operator=(HugeData &&other) noexcept {
        cout << "移动赋值运算符" << endl;
 
        if (this != &other) {
            // 释放当前资源
            delete data;
 
            // 窃取对方的资源
            data = other.data;
            other.data = nullptr;
        }
 
        return *this;
    }
};
 
// 使用示例
void useMove() {
    HugeData d1;
    HugeData d2;
 
    d2 = std::move(d1);  // 移动赋值
    // 此后d1处于有效但未指定状态
}

10.4.3 右值引用和std::move

#include <iostream>
#include <string>
using namespace std;
 
void process(int &x) {
    cout << "左值引用: " << x << endl;
}
 
void process(int &&x) {
    cout << "右值引用: " << x << endl;
}
 
void forward(int &&x) {
    // 完美转发
    process(std::forward<int>(x));
}
 
int main() {
    int a = 10;
 
    process(a);           // 左值,调用第一个
    process(20);          // 右值,调用第二个
    process(std::move(a)); // 将左值转为右值引用
 
    // std::move的本质
    // 它只是类型转换,不移动任何东西!
    // 移动操作是由移动构造函数/赋值运算符完成的
 
    string s1 = "Hello";
    string s2 = std::move(s1);  // s1的内容被移动到s2
 
    // s1现在处于有效但未指定状态
    cout << "s1: " << s1 << endl;  // 可能是空字符串
    cout << "s2: " << s2 << endl;  // "Hello"
 
    return 0;
}

10.5 对象的生命周期管理

10.5.1 对象的创建顺序

#include <iostream>
using namespace std;
 
class Member {
    int id;
public:
    Member(int i) : id(i) {
        cout << "Member " << id << " 构造" << endl;
    }
    ~Member() {
        cout << "Member " << id << " 析构" << endl;
    }
};
 
class Container {
    Member m1;
    Member m2;
    int x;
public:
    Container() : m2(2), m1(1), x(10) {
        // 注意:初始化顺序由声明顺序决定,不是初始化列表顺序!
        cout << "Container 构造" << endl;
    }
    ~Container() {
        cout << "Container 析构" << endl;
    }
};
 
int main() {
    Container c;
    return 0;
}
// 输出顺序:
// Member 1 构造
// Member 2 构造
// Container 构造
// Container 析构
// Member 2 析构
// Member 1 析构

10.5.2 静态对象的生命周期

#include <iostream>
using namespace std;
 
class Singleton {
private:
    static Singleton instance;
    int value;
 
    Singleton() : value(0) {
        cout << "Singleton 构造" << endl;
    }
 
public:
    static Singleton &getInstance() {
        return instance;
    }
 
    void setValue(int v) { value = v; }
    int getValue() const { return value; }
};
 
// 静态成员定义
Singleton Singleton::instance;
 
class LocalStatic {
public:
    LocalStatic() {
        cout << "LocalStatic 构造" << endl;
    }
    ~LocalStatic() {
        cout << "LocalStatic 析构" << endl;
    }
};
 
void useLocalStatic() {
    // 局部静态对象,第一次调用时构造,程序结束时析构
    static LocalStatic ls;
    cout << "使用局部静态对象" << endl;
}
 
int main() {
    cout << "main开始" << endl;
 
    Singleton &s = Singleton::getInstance();
    s.setValue(100);
 
    useLocalStatic();
    useLocalStatic();  // 不会再次构造
 
    cout << "main结束" << endl;
    return 0;
}

10.5.3 动态对象的生命周期

#include <iostream>
using namespace std;
 
class Tracked {
    static int count;
    int id;
public:
    Tracked() : id(++count) {
        cout << "对象 " << id << " 构造,当前总数: " << count << endl;
    }
    ~Tracked() {
        cout << "对象 " << id << " 析构,当前总数: " << --count << endl;
    }
};
 
int Tracked::count = 0;
 
void dynamicLifeDemo() {
    cout << "=== 栈对象 ===" << endl;
    Tracked t1;  // 构造
    {  // 作用域开始
        Tracked t2;  // 构造
    }  // t2析构
 
    cout << "\n=== 堆对象 ===" << endl;
    Tracked *p1 = new Tracked();   // 构造
    Tracked *p2 = new Tracked[3];  // 构造3个
 
    delete p1;     // p1析构
    delete[] p2;   // 3个对象析构
}
 
int main() {
    dynamicLifeDemo();
    cout << "函数结束" << endl;
    return 0;
}

10.6 资源管理类(RAII)

10.6.1 智能指针的实现原理

#include <iostream>
using namespace std;
 
// 简化版的unique_ptr
template <typename T>
class UniquePtr {
private:
    T *ptr;
 
public:
    explicit UniquePtr(T *p = nullptr) : ptr(p) {}
 
    // 禁止拷贝
    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;
    }
 
    ~UniquePtr() {
        delete ptr;
    }
 
    T &operator*() const { return *ptr; }
    T *operator->() const { return ptr; }
    T *get() const { return ptr; }
 
    T *release() {
        T *temp = ptr;
        ptr = nullptr;
        return temp;
    }
 
    void reset(T *p = nullptr) {
        delete ptr;
        ptr = p;
    }
};
 
int main() {
    UniquePtr<int> p1(new int(10));
    cout << *p1 << endl;
 
    UniquePtr<int> p2 = move(p1);
    // p1现在是空指针
 
    return 0;
}

10.7 综合示例

实现一个安全的字符串类

#include <iostream>
#include <cstring>
#include <utility>
using namespace std;
 
class SafeString {
private:
    char *data;
    int length;
    int capacity;
 
    void ensureCapacity(int minCapacity) {
        if (minCapacity > capacity) {
            int newCapacity = max(minCapacity, capacity * 2);
            char *newData = new char[newCapacity + 1];
            if (data) {
                strcpy(newData, data);
            }
            delete[] data;
            data = newData;
            capacity = newCapacity;
        }
    }
 
public:
    // 默认构造函数
    SafeString() : data(nullptr), length(0), capacity(0) {}
 
    // C字符串构造函数
    SafeString(const char *str) {
        if (str) {
            length = strlen(str);
            capacity = length;
            data = new char[capacity + 1];
            strcpy(data, str);
        } else {
            data = nullptr;
            length = capacity = 0;
        }
    }
 
    // 拷贝构造函数
    SafeString(const SafeString &other) 
        : length(other.length), capacity(other.capacity) {
        if (other.data) {
            data = new char[capacity + 1];
            strcpy(data, other.data);
        } else {
            data = nullptr;
        }
    }
 
    // 移动构造函数
    SafeString(SafeString &&other) noexcept 
        : data(other.data), length(other.length), capacity(other.capacity) {
        other.data = nullptr;
        other.length = 0;
        other.capacity = 0;
    }
 
    // 析构函数
    ~SafeString() {
        delete[] data;
    }
 
    // 拷贝赋值运算符
    SafeString &operator=(const SafeString &other) {
        if (this != &other) {
            SafeString temp(other);  // 拷贝构造临时对象
            swap(temp.data, data);
            swap(temp.length, length);
            swap(temp.capacity, capacity);
        }
        return *this;
    }
 
    // 移动赋值运算符
    SafeString &operator=(SafeString &&other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            length = other.length;
            capacity = other.capacity;
            other.data = nullptr;
            other.length = 0;
            other.capacity = 0;
        }
        return *this;
    }
 
    // 追加操作
    SafeString &append(const char *str) {
        if (str) {
            int strLen = strlen(str);
            ensureCapacity(length + strLen);
            strcat(data + length, str);
            length += strLen;
        }
        return *this;
    }
 
    SafeString &append(const SafeString &other) {
        return append(other.data);
    }
 
    // 索引访问
    char &operator[](int index) {
        return data[index];
    }
 
    const char &operator[](int index) const {
        return data[index];
    }
 
    // 获取C字符串
    const char *c_str() const { return data ? data : ""; }
    int size() const { return length; }
    bool empty() const { return length == 0; }
 
    void print() const {
        cout << (data ? data : "(empty)") << endl;
    }
};
 
int main() {
    SafeString s1("Hello");
    SafeString s2 = s1;      // 拷贝
    SafeString s3;
    s3 = s1;                 // 赋值
 
    s1.append(", World!");
    s1.print();              // Hello, World!
    s2.print();              // Hello
 
    SafeString s4 = move(s1); // 移动
    s4.print();               // Hello, World!
 
    return 0;
}

10.8 习题

基础题

1. 构造函数练习

 创建一个Book类,包含书名、作者、价格。实现:
 - 默认构造函数
 - 带参数的构造函数
 - 委托构造函数
 - 在构造函数中使用初始化列表

2. 拷贝控制

 分析以下代码的输出,解释为什么:
class Test {
    int id;
public:
    Test(int i) : id(i) { cout << "构造 " << id << endl; }
    Test(const Test &t) : id(t.id) { cout << "拷贝 " << id << endl; }
    ~Test() { cout << "析构 " << id << endl; }
};
 
Test func() {
    Test t(1);
    return t;
}
 
int main() {
    Test t2 = func();
    return 0;
}

3. 成员初始化顺序

 编写程序验证成员初始化的顺序是由声明顺序决定的,而不是初始化列表中的顺序。

进阶题

4. 资源管理类

 实现一个FileHandle类,使用RAII模式管理文件资源:
 - 构造函数打开文件
 - 析构函数关闭文件
 - 禁止拷贝,允许移动
 - 提供write和read方法

5. 深拷贝与浅拷贝

 创建两个类ShallowCopy和DeepCopy,分别包含一个动态分配的数组成员。演示:
 - 浅拷贝导致的问题
 - 深拷贝如何解决问题

6. 移动语义应用

 实现一个Buffer类,管理一块动态内存。实现:
 - 拷贝构造函数(深拷贝)
 - 移动构造函数
 - 拷贝赋值运算符
 - 移动赋值运算符
 比较拷贝和移动的性能差异。

综合题

7. 对象池实现

 设计一个对象池类ObjectPool<T>:
 - 预分配一定数量的对象
 - 提供acquire方法获取对象
 - 提供release方法归还对象
 - 使用RAII确保资源安全

8. 完整的字符串类

 基于本章的SafeString,添加以下功能:
 - 比较运算符(==, !=, <, >)
 - 拼接运算符(+)
 - 查找子串
 - 获取子串
 - 插入和删除

9. 生命周期追踪器

 创建一个ObjectTracker类,能够:
 - 统计当前存活的对象数量
 - 追踪对象的构造和析构
 - 输出对象生命周期报告

思考题

10. 深度分析

  1. 解释拷贝省略(Copy Elision)和返回值优化(RVO)
  2. 讨论何时应该禁用拷贝,只允许移动
  3. 分析移动后对象的状态和使用限制
  4. 讨论异常安全性和强异常保证

10.9 小结

本章详细学习了C++对象的生命周期管理:

- 构造函数 负责对象的初始化,支持重载和委托 - 析构函数 负责对象的清理,遵循栈的后进先出原则 - 初始化列表 是初始化成员的首选方式,对于const和引用成员是必须的 - 拷贝控制 包括拷贝构造函数和拷贝赋值运算符,需要注意深拷贝 - 移动语义 (C++11)允许高效地转移资源所有权 - RAII 是资源管理的核心原则,确保资源的安全获取和释放

理解对象的生命周期对于编写高效、安全的C++代码至关重要,特别是在涉及资源管理和异常处理的场景下。