C/C++内置库函数(5):值/引用传递、移动构造、以及常用的构造技巧

引用传递(左值引用 T& / 右值引用 T&& 作为函数参数)的本质是 "绑定到已有对象",不会创建新对象,因此不会触发被引用对象的构造函数(构造函数仅在 "创建新对象" 时调用)。

一、核心逻辑:引用是 "别名",不是新对象

引用(无论左值 / 右值引用)的本质是已有对象的别名,而非独立对象。当函数参数是引用时:

  • 形参仅 "绑定" 到实参对象(左值引用绑左值,右值引用绑右值);
  • 没有分配新的内存,没有创建新对象;
  • 构造函数的唯一作用是 "初始化新对象"------ 没有新对象,自然不会触发构造函数(拷贝构造、移动构造、默认构造都不会)

示例验证:引用传递不触发构造

cpp 复制代码
#include <iostream>
using namespace std;

class A {
public:
    A() { cout << "默认构造" << endl; }
    A(const A&) { cout << "拷贝构造" << endl; }
    A(A&&) { cout << "移动构造" << endl; }
};

// 左值引用参数
void func1(A& a) {}
// 右值引用参数
void func2(A&& a) {}
// 值传递参数(对比组)
void func3(A a) {}

int main() {
    A a; // 触发默认构造(创建a)
    cout << "--- 左值引用传递 ---" << endl;
    func1(a); // 仅绑定a,无构造
    cout << "--- 右值引用传递 ---" << endl;
    func2(std::move(a)); // 仅绑定a(转为右值),无构造
    cout << "--- 值传递 ---" << endl;
    func3(a); // 创建新对象,触发拷贝构造
    return 0;
}
  1. QueryCallback(T&):仅接收左值,右值直接编译报错;
  2. QueryCallback(const T&):唯一能同时接收左值 + 右值的参数类型;
  3. QueryCallback(T&&):仅接收右值 /std::move 后的左值,普通左值报错。

二、移动构造:

类1:采用编译器默认的移动构造

1. 默认移动构造的「生成条件」

编译器不会无条件生成默认移动构造,必须满足以下核心条件(C++11+ 规则):

  1. 类没有自定义的拷贝构造、拷贝赋值运算符、析构函数(即 "三法则" 中若自定义了其中一个,编译器会禁用默认移动);
  2. 类的所有非静态成员都是可移动的 (即成员类型有可用的移动构造,没有被 = delete 禁用);
  3. 类的所有基类都是可移动的(基类有可用的移动构造)。

2. 默认移动构造的「核心行为」

编译器按成员声明顺序,对每个非静态成员执行以下逻辑:

成员类型 处理方式 示例(QueryCallback 的成员)
可移动的资源型成员 调用成员的移动构造(std::move(源成员)),转移资源所有权(改指针 / 句柄) future_/cb_:调用std::future/std::function的移动构造
基础类型(int/bool 等) 直接拷贝数值(无 "移动" 概念,等价于浅拷贝) timeout_:直接赋值other.timeout_
容器型成员(string/vector) 调用容器的移动构造(转移内部指针,无数据拷贝) 若有std::string name_name_(std::move(other.name_))
不可移动的成员 编译器拒绝生成默认移动构造(编译报错) 若有std::mutex mtx_(不可移动):无默认移动构造
静态成员 完全不处理(静态成员属于类,而非对象) 若有static int count_:默认移动构造不会碰它

3.默认移动构造的「关键特性」

3.1 noexcept 判定规则

默认移动构造的 noexcept 标记遵循 "全成员原则":

  • 仅当所有成员的移动构造都是 noexcept 时,默认移动构造才会被标记为 noexcept
  • 若有任意成员的移动构造不是 noexcept,默认移动构造也不会带 noexcept(所以建议手写移动构造,这样可以显式加 noexcept)。
3.2 源对象的状态

移动后,源对象(other)的状态由其成员的移动构造决定:

  • 资源型成员(unique_ptr/future/function):源对象的成员会被置空 / 失效 (比如 unique_ptr 变为 nullptrfuture 变为无状态);
  • 基础类型成员:源对象的数值不变(因为只是拷贝,没有转移);
  • 核心原则:源对象仍处于 "可析构" 状态(不会 double free),但不建议再使用(语义上已 "被掏空")。

类2:自定义类的移动构造

可以同时定义拷贝构造、析构函数、移动构造。

传入右值构造时:如果同时存在移动构造、拷贝构造。会优先调用移动构造,只有当移动构造不可用 (不存在、被 delete、编译错误)时,才会降级匹配「能绑定右值的 const T& 拷贝构造」;若拷贝构造也不可用,最终才会编译报错。

cpp 复制代码
class QueryCallback {
public:
    // 自定义构造
    QueryCallback(std::future<std::unique_ptr<sql::ResultSet>> &&future, std::function<void(std::unique_ptr<sql::ResultSet>)> &&cb)
        : future_(std::move(future)), cb_(std::move(cb)) {}

    // 手动定义移动构造(哪怕有拷贝/析构,依然生效)
    QueryCallback(QueryCallback&& other) noexcept {
        std::cout << "执行手动移动构造" << std::endl;
        future_ = std::move(other.future_);
        cb_ = std::move(other.cb_);
    }

    // 自定义拷贝构造(仅示例,实际你的场景中std::future不可拷贝,会编译报错,这里仅演示规则)
    QueryCallback(const QueryCallback& other) = delete; // 必须delete,否则编译失败(std::future不可拷贝)

    // 自定义析构
    ~QueryCallback() {}

private:
    std::future<std::unique_ptr<sql::ResultSet>> future_;
    std::function<void(std::unique_ptr<sql::ResultSet>)> cb_;
};

"移动构造不存在" 的场景

  1. 移动构造不存在(比如自定义析构导致编译器禁用默认移动构造,且未手动定义);
  2. 移动构造被显式删除QueryCallback(QueryCallback&&) = delete;);
  3. 移动构造编译错误(比如成员移动时存在语法错误)。

场景 1:移动构造 + 拷贝构造都存在 → 优先调用移动构造

cpp 复制代码
class QueryCallback {
public:
    QueryCallback() = default;
    // 移动构造(接收右值引用)
    QueryCallback(QueryCallback&&) { 
        std::cout << "调用移动构造" << std::endl; 
    }
    // 拷贝构造(接收const左值引用)
    QueryCallback(const QueryCallback&) { 
        std::cout << "调用拷贝构造" << std::endl; 
    }
};

int main() {
    std::vector<QueryCallback> pending_queries_;
    QueryCallback cb;
    
    // 传入右值(std::move)→ 优先匹配移动构造
    pending_queries_.emplace_back(std::move(cb)); 
    // 输出:调用移动构造
    return 0;
}

场景 2:移动构造被 delete(不可用)→ 降级调用拷贝构造

cpp 复制代码
class QueryCallback {
public:
    QueryCallback() = default;
    // 移动构造被删除(不可用)
    QueryCallback(QueryCallback&&) = delete;
    // 拷贝构造可用
    QueryCallback(const QueryCallback&) { 
        std::cout << "调用拷贝构造" << std::endl; 
    }
};

int main() {
    std::vector<QueryCallback> pending_queries_;
    QueryCallback cb;
    
    // 移动构造不可用 → 降级匹配拷贝构造(const T& 绑定右值)
    pending_queries_.emplace_back(std::move(cb)); 
    // 输出:调用拷贝构造
    return 0;
}

场景 3:移动构造不存在 + 拷贝构造可用 → 降级调用拷贝构造

cpp 复制代码
class QueryCallback {
public:
    QueryCallback() = default;
    // 自定义析构 → 编译器禁用默认移动构造(移动构造不存在)
    ~QueryCallback() {}
    // 拷贝构造可用(成员可拷贝)
    QueryCallback(const QueryCallback&) { 
        std::cout << "调用拷贝构造" << std::endl; 
    }
private:
    int timeout_ = 5000; // 可拷贝成员
};

int main() {
    std::vector<QueryCallback> pending_queries_;
    QueryCallback cb;
    
    // 移动构造不存在 → 降级匹配拷贝构造
    pending_queries_.emplace_back(std::move(cb)); 
    // 输出:调用拷贝构造
    return 0;
}

场景 4:移动构造不可用 + 拷贝构造不可用 → 编译报错

cpp 复制代码
class QueryCallback {
public:
    QueryCallback() = default;
    // 移动构造被删除
    QueryCallback(QueryCallback&&) = delete;
    // 拷贝构造被删除(或成员不可拷贝,比如std::future)
    QueryCallback(const QueryCallback&) = delete;
};

int main() {
    std::vector<QueryCallback> pending_queries_;
    QueryCallback cb;
    
    // 移动/拷贝构造都不可用 → 编译报错
    pending_queries_.emplace_back(std::move(cb)); 
    return 0;
}

三、各种构造函数:

模板 1:类的默认移动构造 / 移动赋值(显式声明)

场景 :类包含可移动资源(std::unique_ptr/std::future/std::string 等),需要编译器生成高效的默认移动逻辑

企业级要求 :必须加 noexcept(避免容器如 std::vector 降级为拷贝),显式 default 避免隐式生成的构造不符合预期。

cpp 复制代码
class QueryCallback {
public:
    // 1. 移动构造(显式默认,企业级必加noexcept)
    QueryCallback(QueryCallback&&) noexcept = default;
    // 2. 移动赋值运算符(配套移动构造,完整值语义)
    QueryCallback& operator=(QueryCallback&&) noexcept = default;

    // 带右值引用参数的构造(接管外部对象所有权)
    QueryCallback(std::future<std::unique_ptr<sql::ResultSet>>&& future,
                  std::function<void(std::unique_ptr<sql::ResultSet>)>&& cb)
        : future_(std::move(future)), cb_(std::move(cb)) {}

private:
    std::future<std::unique_ptr<sql::ResultSet>> future_;
    std::function<void(std::unique_ptr<sql::ResultSet>)> cb_;
};

模板 2:深拷贝构造 / 拷贝赋值(资源管理核心):

场景:类包含动态分配资源(如文件句柄、自定义内存缓冲区、数据库连接),需避免浅拷贝导致的资源重复释放。

企业级要求:处理自赋值、先释放当前资源、再深拷贝新资源。

cpp 复制代码
class DatabaseConnection {
public:
    // 深拷贝构造
    DatabaseConnection(const DatabaseConnection& other) {
        // 深拷贝动态资源(避免浅拷贝共享句柄)
        this->conn_fd_ = dup(other.conn_fd_); // 复制文件句柄
        this->buf_ = new char[other.buf_size_];
        memcpy(this->buf_, other.buf_, other.buf_size_);
        this->buf_size_ = other.buf_size_;
    }

    // 深拷贝赋值(企业级必处理自赋值+资源释放)
    DatabaseConnection& operator=(const DatabaseConnection& other) {
        if (this == &other) return *this; // 防止自赋值

        // 第一步:释放当前对象的资源
        close(this->conn_fd_);
        delete[] this->buf_;

        // 第二步:深拷贝新资源
        this->conn_fd_ = dup(other.conn_fd_); // 这里创建新的fd指向跟other_fd相同的文件
        this->buf_ = new char[other.buf_size_];
        memcpy(this->buf_, other.buf_, other.buf_size_);
        this->buf_size_ = other.buf_size_;

        return *this;
    }

    // 析构函数(释放资源,RAII核心)
    ~DatabaseConnection() {
        close(conn_fd_);
        delete[] buf_;
    }

private:
    int conn_fd_ = -1;       // 数据库连接句柄
    char* buf_ = nullptr;    // 动态缓冲区
    size_t buf_size_ = 0;    // 缓冲区大小
};

注意:dup的使用:

操作 进程 FD 表 系统文件表项 inode 表 偏移量是否同步
dup(old_fd) 新增 FD → 指向同一个文件表项 共享 1 个文件表项 共享 ✅ 同步
两次 open 同一文件 新增 FD → 指向不同的文件表项 生成 2 个独立文件表项 共享 ❌ 不同步

int new_fd = dup(old_fd);

lseek(old_fd, 0, SEEK_SET); // new_fd 的编译量也跟着变化为0 !!!

close(old_fd);

new_fd还可以正常使用

模板 3:委托构造函数(减少代码冗余)

场景:类有多个带参数的构造函数,需复用核心初始化逻辑(如初始化套接字、加载配置)。

企业级价值:避免多个构造函数重复写相同逻辑,降低维护成本

cpp 复制代码
class HttpClient {
public:
    // 核心构造函数(包含所有初始化逻辑)
    HttpClient(const std::string& url, int timeout, bool ssl)
        : url_(url), timeout_(timeout), ssl_(ssl) {
        init_socket(); // 初始化套接字(核心逻辑只写一次)
    }

    // 委托构造1:默认超时(复用核心构造)
    HttpClient(const std::string& url, bool ssl)
        : HttpClient(url, 5000, ssl) {}

    // 委托构造2:默认SSL+超时(复用核心构造)
    HttpClient(const std::string& url)
        : HttpClient(url, 5000, true) {}

private:
    void init_socket() { /* 初始化网络套接字 */ }

    std::string url_;
    int timeout_;
    bool ssl_;
    int sock_fd_ = -1;
};

模板 4:完美转发构造函数(万能构造,泛型编程)

场景 :泛型类(如容器、工厂类、任务包装类)需要接收任意类型 / 值类别的参数,转发给内部对象的构造。企业级价值:适配任意参数类型,是泛型编程(如线程池、资源包装器)的核心。

cpp 复制代码
// 泛型包装类(企业级常用:任务包装、资源封装)
template <typename T>
class ResourceWrapper {
public:
    // 完美转发构造(万能引用 + std::forward)
    template <typename... Args>
    explicit ResourceWrapper(Args&&... args)
        : obj_(std::forward<Args>(args)...) { // 转发参数给T的构造
    }

private:
    T obj_; // 内部封装的对象
};

// 使用示例(适配任意构造参数+值类别)
std::function<void(std::unique_ptr<sql::ResultSet>)> cb = ...;
ResourceWrapper<QueryCallback> wrapper1(std::move(future), std::move(cb));
ResourceWrapper<std::function<void(std::unique_ptr<sql::ResultSet>)>> wrapper2(std::move(cb));

模板 5:显式构造函数(防止隐式类型转换)

场景:单参数构造函数,避免隐式类型转换导致的逻辑错误(企业级防坑必备)。

企业级要求 :单参数构造函数必须加 explicit,禁止隐式转换。

cpp 复制代码
class Timeout {
public:
    // 显式构造:禁止 int → Timeout 的隐式转换
    explicit Timeout(int ms) : ms_(ms) {}

private:
    int ms_;
};

// 企业级防坑:隐式转换被禁止
void set_timeout(Timeout t) {}
// set_timeout(1000); // 编译报错(必须显式构造)
set_timeout(Timeout(1000)); // 合法

模板 6:继承构造函数(复用基类构造,继承体系)

场景:派生类需要复用基类的构造函数,避免重复声明(如数据库查询的基类 / 派生类体系)。

cpp 复制代码
// 基类:基础查询
class BaseQuery {
public:
    BaseQuery(int conn_id) : conn_id_(conn_id) {}
    BaseQuery(const std::string& db_name, int conn_id) : db_name_(db_name), conn_id_(conn_id) {}

private:
    std::string db_name_;
    int conn_id_;
};

// 派生类:SELECT查询(复用基类构造)
class SelectQuery : public BaseQuery {
public:
    // 继承基类所有构造函数(C++11+)
    using BaseQuery::BaseQuery;

    // 派生类扩展构造(新增SQL参数)
    SelectQuery(const std::string& db_name, int conn_id, const std::string& sql)
        : BaseQuery(db_name, conn_id), sql_(sql) {}

private:
    std::string sql_; // 派生类扩展字段
};

模板 7:禁用不必要的构造(防误使用)

场景:类需要禁止默认构造 / 拷贝构造(如单例类、仅可移动的资源类)。

企业级价值:防止创建无效对象、避免浅拷贝 / 隐式初始化导致的 bug。

cpp 复制代码
// 单例类:禁用默认构造/拷贝构造/赋值
class SingletonConfig {
public:
    // 禁用默认构造(必须通过 get_instance 获取实例)
    SingletonConfig() = delete;
    // 禁用拷贝构造/赋值(防止多实例)
    SingletonConfig(const SingletonConfig&) = delete;
    SingletonConfig& operator=(const SingletonConfig&) = delete;
    // 禁用移动构造/赋值(单例无需移动)
    SingletonConfig(SingletonConfig&&) = delete;
    SingletonConfig& operator=(SingletonConfig&&) = delete;

    // 单例获取接口
    static SingletonConfig& get_instance() {
        static SingletonConfig instance;
        return instance;
    }

private:
    // 私有构造(仅内部创建)
    SingletonConfig() { load_config(); }
    void load_config() { /* 加载配置 */ }
};

四、单例模式的构造:

单例模式的核心构造设计原则是:禁止外部创建实例(私有构造)+ 禁止拷贝 / 移动(避免多实例)+ 全局唯一访问点。以下是两种企业级最常用的单例实现(覆盖饿汉式、懒汉式,均包含完整的构造函数设计

1. 饿汉式单例(预初始化,线程安全,适合轻量资源)

核心特点:程序启动时就初始化单例实例(静态成员变量),天然线程安全;构造函数私有,禁用所有拷贝 / 移动语义。

cpp 复制代码
#include <mutex>
#include <string>
#include <stdexcept>

// 企业级饿汉式单例:配置管理器(常用场景)
class ConfigManager {
public:
    // 全局唯一访问点(const引用,避免外部修改)
    static const ConfigManager& get_instance() {
        return instance_; // 预初始化的静态实例
    }

    // 业务接口:读取配置
    std::string get_config(const std::string& key) const {
        if (config_map_.find(key) == config_map_.end()) {
            throw std::runtime_error("Config key not found: " + key);
        }
        return config_map_.at(key);
    }

    // ===== 核心:禁用所有非单例的构造/赋值 =====
    // 1. 禁用默认构造(外部无法调用,仅内部初始化)
    ConfigManager() = delete;
    // 2. 禁用拷贝构造(防止复制出多个实例)
    ConfigManager(const ConfigManager&) = delete;
    // 3. 禁用拷贝赋值
    ConfigManager& operator=(const ConfigManager&) = delete;
    // 4. 禁用移动构造(C++11+,防止通过移动创建新实例)
    ConfigManager(ConfigManager&&) = delete;
    // 5. 禁用移动赋值
    ConfigManager& operator=(ConfigManager&&) = delete;

private:
    // 私有构造函数(仅内部调用,初始化配置)
    ConfigManager() {
        // 企业级场景:从文件/环境变量加载配置
        load_config_from_file("/etc/app/config.json");
    }

    // 私有析构(可选:防止外部delete,单例生命周期随程序结束)
    ~ConfigManager() = default;

    // 私有方法:加载配置(核心初始化逻辑)
    void load_config_from_file(const std::string& path) {
        // 模拟加载配置
        config_map_["db_host"] = "127.0.0.1";
        config_map_["db_port"] = "3306";
        config_map_["timeout"] = "5000";
    }

    // 静态成员:程序启动时初始化(饿汉式核心)
    static ConfigManager instance_;
    // 配置存储(企业级常用std::unordered_map,此处简化)
    std::unordered_map<std::string, std::string> config_map_;
};

// 静态实例初始化(程序启动时执行,仅一次)
ConfigManager ConfigManager::instance_;

// 使用示例
int main() {
    const auto& config = ConfigManager::get_instance();
    std::cout << "DB Host: " << config.get_config("db_host") << std::endl;
    return 0;
}

2. 懒汉式单例(懒加载,线程安全,适合重量级资源)

核心特点 :第一次调用get_instance时才初始化实例;C++11 后static局部变量天然线程安全(编译器保证),无需手动加锁;构造函数私有,禁用拷贝 / 移动。

cpp 复制代码
#include <mutex>
#include <memory>
#include <string>

// 企业级懒汉式单例:数据库连接池(重量级资源,懒加载)
class DBConnectionPool {
public:
    // 全局唯一访问点(返回引用,避免智能指针拷贝)
    static DBConnectionPool& get_instance() {
        // C++11+:static局部变量,线程安全初始化(仅一次)
        static DBConnectionPool instance; // static DBConnectionPool instance(arg)也可以实现传参
        return instance;
    }

    // 业务接口:获取数据库连接
    void get_connection() {
        // 模拟获取连接
        std::cout << "Get DB connection from pool" << std::endl;
    }

    // ===== 核心:构造函数设计 =====
    // 1. 禁用拷贝构造
    DBConnectionPool(const DBConnectionPool&) = delete;
    // 2. 禁用拷贝赋值
    DBConnectionPool& operator=(const DBConnectionPool&) = delete;
    // 3. 禁用移动构造
    DBConnectionPool(DBConnectionPool&&) = delete;
    // 4. 禁用移动赋值
    DBConnectionPool& operator=(DBConnectionPool&&) = delete;

private:
    // 私有构造函数(懒加载:第一次调用get_instance时执行)
    DBConnectionPool() {
        // 企业级场景:初始化连接池(创建10个数据库连接)
        init_pool(10);
    }

    // 私有析构(释放连接池资源)
    ~DBConnectionPool() {
        // 释放所有数据库连接
        release_pool();
    }

    // 私有方法:初始化连接池
    void init_pool(int size) {
        // 模拟初始化
        pool_size_ = size;
        std::cout << "Init DB pool with " << size << " connections" << std::endl;
    }

    // 私有方法:释放连接池
    void release_pool() {
        std::cout << "Release DB pool resources" << std::endl;
    }

    // 连接池大小
    int pool_size_ = 0;
};

// 使用示例
int main() {
    // 第一次调用:初始化单例(执行私有构造)
    DBConnectionPool& pool = DBConnectionPool::get_instance();
    pool.get_connection();
    // 第二次调用:直接返回已初始化的实例(无构造)
    DBConnectionPool& pool2 = DBConnectionPool::get_instance();
    pool2.get_connection();
    return 0;
}
相关推荐
qq_310658513 小时前
mediasoup源码走读(十)——producer
服务器·c++·音视频
豆约翰4 小时前
Z字形扫描ccf
java·开发语言·算法
Tipriest_4 小时前
C++ Python使用常用库时如何做欧拉角 ⇄ 四元数转换
c++·python·四元数·欧拉角
小尧嵌入式4 小时前
C语言中的面向对象思想
c语言·开发语言·数据结构·c++·单片机·qt
lionliu05194 小时前
执行上下文 (Execution Context)
开发语言·前端·javascript
nbsaas-boot4 小时前
JWT 与 Session 的实用场景分析:从架构边界到工程落地
java·开发语言·架构
Tim_104 小时前
【C++入门】03、C++整型
java·开发语言·jvm
fpcc4 小时前
跟我学C++中级篇——循环展开的分析
c++·优化
盼哥PyAI实验室4 小时前
Python编码处理:解决12306项目的中文乱码问题
开发语言·python