引用传递(左值引用 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;
}
QueryCallback(T&):仅接收左值,右值直接编译报错;QueryCallback(const T&):唯一能同时接收左值 + 右值的参数类型;QueryCallback(T&&):仅接收右值 /std::move后的左值,普通左值报错。
二、移动构造:
类1:采用编译器默认的移动构造
1. 默认移动构造的「生成条件」
编译器不会无条件生成默认移动构造,必须满足以下核心条件(C++11+ 规则):
- 类没有自定义的拷贝构造、拷贝赋值运算符、析构函数(即 "三法则" 中若自定义了其中一个,编译器会禁用默认移动);
- 类的所有非静态成员都是可移动的 (即成员类型有可用的移动构造,没有被
= delete禁用); - 类的所有基类都是可移动的(基类有可用的移动构造)。
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变为nullptr,future变为无状态); - 基础类型成员:源对象的数值不变(因为只是拷贝,没有转移);
- 核心原则:源对象仍处于 "可析构" 状态(不会 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_;
};
"移动构造不存在" 的场景
- 移动构造不存在(比如自定义析构导致编译器禁用默认移动构造,且未手动定义);
- 移动构造被显式删除 (
QueryCallback(QueryCallback&&) = delete;);- 移动构造编译错误(比如成员移动时存在语法错误)。
场景 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;
}