强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

C/C++ Linux 开发教程(GCC + CMake) / 构造函数、析构函数与 RAII

构造函数、析构函数与 RAII

1. 构造函数类型总览

类型语法示例触发场景
默认构造T()无参创建对象
参数化构造T(int, double)带参创建对象
拷贝构造T(const T&)拷贝初始化
移动构造T(T&&)移动初始化
委托构造T() : T(0, 0.0)转发到其他构造函数
#include <iostream>
#include <string>

class Resource {
    std::string name_;
    int* data_;
    size_t size_;

public:
    // 默认构造
    Resource() : name_("empty"), data_(nullptr), size_(0) {
        std::cout << "[默认构造] " << name_ << "\n";
    }

    // 参数化构造
    Resource(const std::string& name, size_t size)
        : name_(name), size_(size) {
        data_ = new int[size]{};
        std::cout << "[参数化构造] " << name_ << " (size=" << size_ << ")\n";
    }

    // 委托构造
    Resource(size_t size) : Resource("unnamed", size) {}

    // 拷贝构造(深拷贝)
    Resource(const Resource& other)
        : name_(other.name_ + "_copy"), size_(other.size_) {
        data_ = new int[size_];
        std::copy(other.data_, other.data_ + size_, data_);
        std::cout << "[拷贝构造] " << name_ << "\n";
    }

    // 移动构造
    Resource(Resource&& other) noexcept
        : name_(std::move(other.name_)), data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;
        other.size_ = 0;
        std::cout << "[移动构造] " << name_ << "\n";
    }

    // 析构函数
    ~Resource() {
        std::cout << "[析构] " << name_ << "\n";
        delete[] data_;
    }

    void print() const {
        std::cout << name_ << " (size=" << size_
                  << ", data=" << static_cast<void*>(data_) << ")\n";
    }
};

int main() {
    Resource a;                     // 默认构造
    Resource b("buffer", 100);      // 参数化构造
    Resource c(50);                 // 委托构造
    Resource d = b;                 // 拷贝构造
    Resource e = std::move(b);      // 移动构造

    std::cout << "--- 对象状态 ---\n";
    a.print();
    b.print();  // 移动后为空
    d.print();
    e.print();

    return 0;  // 析构顺序与构造相反
}

2. 初始化列表

初始化列表在构造函数体执行之前初始化成员变量,效率更高。

#include <iostream>
#include <string>

class Config {
    const int id_;              // const 成员必须用初始化列表
    const std::string& ref_;   // 引用成员必须用初始化列表
    std::string name_;
    int value_;

public:
    // ✅ 正确:使用初始化列表
    Config(int id, const std::string& ref, const std::string& name, int value)
        : id_(id)
        , ref_(ref)
        , name_(name)
        , value_(value)
    {
        std::cout << "Config created: id=" << id_ << "\n";
    }

    void print() const {
        std::cout << "id=" << id_
                  << ", name=" << name_
                  << ", value=" << value_ << "\n";
    }
};

int main() {
    std::string refStr = "reference";
    Config cfg(1, refStr, "my_config", 42);
    cfg.print();
    return 0;
}

⚠️ 注意:成员初始化顺序取决于声明顺序,而非初始化列表中的书写顺序。

class Foo {
    int a_;
    int b_;

public:
    // ⚠️ 危险:b_ 在 a_ 之前声明,所以 b_ 先初始化
    // 这里 b_ 使用了尚未初始化的 a_!
    Foo(int val) : b_(val), a_(b_) {}  // a_ 是未定义值!

    // ✅ 正确:按声明顺序初始化
    Foo(int val) : a_(val), b_(a_) {}
};

3. 析构函数

析构函数在对象生命周期结束时自动调用,用于释放资源。

#include <iostream>
#include <fstream>
#include <string>

class FileHandler {
    std::string filename_;
    std::ofstream file_;
    bool isOpen_;

public:
    FileHandler(const std::string& filename)
        : filename_(filename), isOpen_(false) {
        file_.open(filename);
        if (file_.is_open()) {
            isOpen_ = true;
            std::cout << "打开文件: " << filename_ << "\n";
        }
    }

    ~FileHandler() {
        // 析构时自动清理资源
        if (isOpen_) {
            file_.close();
            std::cout << "关闭文件: " << filename_ << "\n";
        }
    }

    void write(const std::string& data) {
        if (isOpen_) {
            file_ << data;
        }
    }

    // 禁止拷贝(资源语义)
    FileHandler(const FileHandler&) = delete;
    FileHandler& operator=(const FileHandler&) = delete;
};

int main() {
    {
        FileHandler fh("/tmp/test.txt");
        fh.write("Hello RAII\n");
        fh.write("自动关闭文件\n");
    }  // fh 离开作用域 → ~FileHandler() 自动调用

    std::cout << "文件已自动关闭\n";
    return 0;
}

4. RAII(资源获取即初始化)

RAII 是 C++ 最重要的编程范式:在构造函数中获取资源,在析构函数中释放资源

典型 RAII 资源类型

资源类型RAII 包装
堆内存std::unique_ptr, std::shared_ptr
文件std::fstream
互斥锁std::lock_guard, std::unique_lock
网络连接自定义类
数据库连接自定义类

自定义 RAII:互斥锁守卫

#include <iostream>
#include <mutex>

// 模拟一个 RAII 锁守卫
template <typename Mutex>
class LockGuard {
    Mutex& mtx_;

public:
    explicit LockGuard(Mutex& mtx) : mtx_(mtx) {
        mtx_.lock();
        std::cout << "🔒 已加锁\n";
    }

    ~LockGuard() {
        mtx_.unlock();
        std::cout << "🔓 已解锁\n";
    }

    // 禁止拷贝和移动
    LockGuard(const LockGuard&) = delete;
    LockGuard& operator=(const LockGuard&) = delete;
};

class SimpleMutex {
public:
    void lock()   { /* 实际加锁逻辑 */ }
    void unlock() { /* 实际解锁逻辑 */ }
};

SimpleMutex g_mutex;

void criticalSection() {
    LockGuard<SimpleMutex> guard(g_mutex);  // 构造时加锁
    std::cout << "执行临界区操作...\n";
    // 如果这里抛出异常,guard 的析构函数仍然会被调用 → 自动解锁
}  // guard 离开作用域 → 自动解锁

int main() {
    criticalSection();
    return 0;
}

5. Rule of Three / Five / Zero

规则内容何时适用
Rule of Three如果需要自定义 析构/拷贝构造/拷贝赋值 中的任意一个,通常三个都需要管理资源的类
Rule of Five在 Three 的基础上加上 移动构造/移动赋值C++11 移动语义
Rule of Zero让编译器生成所有特殊成员函数;资源管理委托给 RAII 类型推荐的现代 C++ 风格

Rule of Five 示例

#include <iostream>
#include <cstring>

class Buffer {
    char* data_;
    size_t size_;

public:
    explicit Buffer(size_t size = 0)
        : data_(size ? new char[size]{} : nullptr), size_(size) {}

    // 1. 析构函数
    ~Buffer() {
        delete[] data_;
        std::cout << "~Buffer\n";
    }

    // 2. 拷贝构造
    Buffer(const Buffer& other)
        : data_(other.size_ ? new char[other.size_] : nullptr)
        , size_(other.size_) {
        if (data_) std::memcpy(data_, other.data_, size_);
        std::cout << "拷贝构造\n";
    }

    // 3. 拷贝赋值
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            delete[] data_;
            size_ = other.size_;
            data_ = size_ ? new char[size_] : nullptr;
            if (data_) std::memcpy(data_, other.data_, size_);
        }
        std::cout << "拷贝赋值\n";
        return *this;
    }

    // 4. 移动构造
    Buffer(Buffer&& other) noexcept
        : data_(other.data_), size_(other.size_) {
        other.data_ = nullptr;
        other.size_ = 0;
        std::cout << "移动构造\n";
    }

    // 5. 移动赋值
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data_;
            data_ = other.data_;
            size_ = other.size_;
            other.data_ = nullptr;
            other.size_ = 0;
        }
        std::cout << "移动赋值\n";
        return *this;
    }

    size_t size() const { return size_; }
};

int main() {
    Buffer a(100);          // 参数化构造
    Buffer b = a;           // 拷贝构造
    Buffer c = std::move(a); // 移动构造
    Buffer d;
    d = b;                  // 拷贝赋值
    d = std::move(c);       // 移动赋值

    std::cout << "a.size=" << a.size() << "\n";  // 0(已移动)
    return 0;
}

Rule of Zero 示例(推荐)

#include <iostream>
#include <memory>
#include <string>

// 使用标准库 RAII 类型,不写任何特殊成员函数
class Widget {
    std::string name_;
    std::unique_ptr<int[]> data_;
    size_t size_;

public:
    Widget(const std::string& name, size_t size)
        : name_(name), data_(std::make_unique<int[]>(size)), size_(size) {}

    // 编译器自动生成的特殊成员函数是正确的:
    // - 析构:自动销毁 name_ 和 data_
    // - 拷贝:被 delete(unique_ptr 不可拷贝)
    // - 移动:自动生成且正确

    void print() const {
        std::cout << name_ << " (size=" << size_ << ")\n";
    }
};

int main() {
    Widget a("widget_a", 10);
    // Widget b = a;              // 编译错误:不可拷贝
    Widget c = std::move(a);      // OK:移动语义

    c.print();  // widget_a (size=10)
    a.print();  // (空名称,size=0)

    return 0;
}

6. 拷贝语义 vs 移动语义

语义操作源对象状态性能
拷贝复制数据到新对象不变O(n)
移动窃取资源指针有效但未指定状态O(1)
#include <iostream>
#include <vector>
#include <string>
#include <chrono>

// 模拟大对象拷贝 vs 移动
class LargeBuffer {
    std::vector<int> data_;

public:
    explicit LargeBuffer(size_t n) : data_(n, 42) {}

    // 拷贝构造 — 深拷贝
    LargeBuffer(const LargeBuffer& other) : data_(other.data_) {
        std::cout << "拷贝: " << data_.size() << " 个元素\n";
    }

    // 移动构造 — O(1) 窃取内部缓冲区
    LargeBuffer(LargeBuffer&& other) noexcept : data_(std::move(other.data_)) {
        std::cout << "移动: O(1) 完成\n";
    }
};

int main() {
    LargeBuffer buf(1'000'000);

    auto t1 = std::chrono::steady_clock::now();
    LargeBuffer copy = buf;  // 拷贝:O(n)
    auto t2 = std::chrono::steady_clock::now();
    LargeBuffer moved = std::move(buf);  // 移动:O(1)
    auto t3 = std::chrono::steady_clock::now();

    auto copy_us = std::chrono::duration_cast<std::chrono::microseconds>(t2 - t1).count();
    auto move_us = std::chrono::duration_cast<std::chrono::microseconds>(t3 - t2).count();

    std::cout << "拷贝耗时: " << copy_us << " μs\n";
    std::cout << "移动耗时: " << move_us << " μs\n";

    return 0;
}

7. std::move 与 std::forward

#include <iostream>
#include <utility>
#include <string>

// std::move:无条件转换为右值引用
// std::forward:仅在实参为右值时转换(完美转发)

class Widget {
    std::string name_;

public:
    explicit Widget(const std::string& name) : name_(name) {
        std::cout << "构造: " << name_ << "\n";
    }

    Widget(const Widget& other) : name_(other.name_) {
        std::cout << "拷贝: " << name_ << "\n";
    }

    Widget(Widget&& other) noexcept : name_(std::move(other.name_)) {
        std::cout << "移动: " << name_ << "\n";
    }
};

// 完美转发工厂函数
template <typename T, typename... Args>
T create(Args&&... args) {
    return T(std::forward<Args>(args)...);
}

int main() {
    std::string s = "hello";

    // std::move:强制移动
    std::string s2 = std::move(s);  // s 被移动,不再持有数据
    std::cout << "s2=" << s2 << ", s=\"" << s << "\"\n";

    // 完美转发
    Widget w1("normal");
    Widget w2 = create<Widget>("forwarded");     // 直接构造
    Widget w3 = create<Widget>(std::move(w1));   // 移动构造

    return 0;
}
工具作用条件
std::move(x)无条件将 x 转为右值总是转换
std::forward<T>(x)仅当 T 是右值引用时转换条件转换(完美转发)

⚠️ 注意std::move 本身不做任何移动操作,它只是将参数转换为右值引用。真正的移动发生在移动构造函数/移动赋值中。


8. 编译器生成的默认函数

使用 = default 显式要求编译器生成默认版本:

#include <iostream>

class Defaultable {
    int value_;

public:
    Defaultable() = default;                    // 默认构造
    explicit Defaultable(int v) : value_(v) {}  // 参数化构造

    ~Defaultable() = default;                   // 默认析构
    Defaultable(const Defaultable&) = default;  // 默认拷贝构造
    Defaultable(Defaultable&&) = default;       // 默认移动构造
    Defaultable& operator=(const Defaultable&) = default; // 默认拷贝赋值
    Defaultable& operator=(Defaultable&&) = default;      // 默认移动赋值

    int value() const { return value_; }
};

// = delete 显式禁止某些操作
class NonCopyable {
public:
    NonCopyable() = default;
    NonCopyable(const NonCopyable&) = delete;             // 禁止拷贝
    NonCopyable& operator=(const NonCopyable&) = delete;  // 禁止拷贝赋值
    NonCopyable(NonCopyable&&) = default;                 // 允许移动
    NonCopyable& operator=(NonCopyable&&) = default;
};

int main() {
    Defaultable a(42);
    Defaultable b = a;              // 拷贝
    Defaultable c = std::move(a);   // 移动

    NonCopyable d;
    // NonCopyable e = d;           // 编译错误:拷贝已删除
    NonCopyable f = std::move(d);   // OK

    return 0;
}

实际场景:数据库连接池中的 RAII

#include <iostream>
#include <string>
#include <stdexcept>

// 模拟数据库连接
class DBConnection {
    std::string connStr_;
    bool active_;

public:
    explicit DBConnection(const std::string& connStr)
        : connStr_(connStr), active_(true) {
        std::cout << "🔗 建立连接: " << connStr_ << "\n";
    }

    ~DBConnection() {
        if (active_) {
            std::cout << "🔌 关闭连接: " << connStr_ << "\n";
        }
    }

    void execute(const std::string& sql) {
        if (!active_) throw std::runtime_error("连接已关闭");
        std::cout << "📤 执行: " << sql << "\n";
    }

    void close() {
        active_ = false;
        std::cout << "🔌 手动关闭连接\n";
    }
};

// RAII 连接守卫
class ConnectionGuard {
    DBConnection* conn_;

public:
    explicit ConnectionGuard(DBConnection* conn) : conn_(conn) {}
    ~ConnectionGuard() {
        if (conn_) {
            conn_->close();
        }
    }

    DBConnection* operator->() { return conn_; }
    DBConnection& operator*() { return *conn_; }

    // 禁止拷贝
    ConnectionGuard(const ConnectionGuard&) = delete;
    ConnectionGuard& operator=(const ConnectionGuard&) = delete;
};

int main() {
    try {
        DBConnection conn("localhost:5432/mydb");
        ConnectionGuard guard(&conn);

        guard->execute("SELECT * FROM users");
        guard->execute("INSERT INTO logs VALUES ('hello')");

        // 如果这里抛异常,guard 仍会关闭连接
        throw std::runtime_error("模拟错误");
    } catch (const std::exception& e) {
        std::cout << "❌ 捕获异常: " << e.what() << "\n";
    }

    return 0;
}

扩展阅读