size_t
是C++中专门用来表示大小、长度、索引、数量等非负整数值的类型

1.无符号整数溢出
// 里程表:只能显示 0-999999
size_t 里程 = 5; // 显示 000005
size_t 目标 = 10; // 显示 000010
// 你想计算:5 - 10
// 但里程表没有负数!
// 它会从 000005 往前倒...
// 999999 ← 倒9下
// 999998 ← 倒8下
// 999997 ← 倒7下
// 999996 ← 倒6下
// 999995 ← 倒5下 最终停在这里!
2. 平台适配性(自动匹配)
// 在32位系统上:
size_t 大小; // 实际上是 unsigned int (4字节)
int 数值; // 实际上是 int (4字节)
// 在64位系统上:
size_t 大小; // 自动变成 unsigned long long (8字节)
int 数值; // 仍然是 int (4字节) ← 注意!没变!
// 这就是关键:size_t 自动匹配指针大小
// 而 int 保持固定
3.必须使用 size_t 的场景
标准库函数参数和返回值
// 内存操作函数
void* memset(void* ptr, int value, size_t num);
void* memcpy(void* dest, const void* src, size_t num);
void* memmove(void* dest, const void* src, size_t num);
// 字符串操作
size_t strlen(const char* str);
size_t strlcpy(char* dest, const char* src, size_t size);
// 文件操作
size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
则在调用函数时必须使用size_t:
#include <cstring>
#include <cstdio>
int main() {
char buffer[100];
const char* source = "Hello";
// ✅ 必须用 size_t
size_t 长度 = strlen(source); // 返回 size_t
size_t 拷贝长度 = sizeof(buffer);
strlcpy(buffer, source, 拷贝长度); // 参数是 size_t
return 0;
}
接受某些调用的返回值:
cpp
#include <vector>
#include <string>
#include <array>
#include <list>
int main() {
std::vector<int> vec = {1, 2, 3};
std::string str = "Hello";
// ✅ 必须用 size_t
size_t vec大小 = vec.size(); // 返回 size_t
size_t str长度 = str.length(); // 返回 size_t
size_t vec容量 = vec.capacity(); // 返回 size_t
// 遍历容器
for (size_t i = 0; i < vec.size(); ++i) { // ✅
// vec.size() 返回 size_t
}
return 0;
}
构造函数和析构函数
构造函数
作用
构造函数是创建对象时自动调用的特殊函数,用于初始化对象。
语法说明
DataStorageServer(const std::string& db_path,
const std::string& storage_path);
-
函数名 :
DataStorageServer→ 必须与类名相同 -
参数:
-
const std::string& db_path→ 数据库文件路径 -
const std::string& storage_path→ 存储文件路径
-
-
const 引用 :
const std::string&表示-
参数是只读的(不能修改)
-
通过引用传递(避免拷贝开销)
"我保证不修改你,所以请把你的'本体'直接给我用,别费劲拷贝一份新的了。"
-
可以是临时字符串
-
调用时机
// 创建DataStorageServer对象时自动调用
DataStorageServer server("./data.db", "./storage");
// ↑
// 构造函数被调用,传入两个路径参数
构造函数的任务
在这个项目中,构造函数需要:
-
用
db_path初始化数据库连接 -
用
storage_path设置存储目录 -
创建子模块(DatabaseManager, TaskQueue等)
-
初始化内部状态
-
启动必要的后台线程
析构函数
作用
析构函数是对象销毁时自动调用的特殊函数,用于清理资源。
语法说明
~DataStorageServer() = default;
-
函数名 :
~DataStorageServer→ 类名前加波浪号 -
= default:使用编译器生成的默认析构函数
-
无参数:析构函数不能有参数
-
无返回值:不能指定返回类型
调用时机
{
DataStorageServer server("./data.db", "./storage");
// 使用server...
} // ← 右括号处,server对象超出作用域
// 析构函数自动调用,清理资源
用 = default适用于:
= default表示"使用编译器自动生成的析构函数",适用于:
-
没有动态分配的资源时
-
所有成员都有合适的析构函数时
-
不需要特殊清理逻辑时
更好的写法
实际上,DataStorageServer不应该用默认析构函数,因为:
需要的清理工作
~DataStorageServer() {
// 1. 停止所有后台线程
stopMonitoring();
// 2. 停止任务队列
task_queue.stop();
// 3. 等待任务完成
task_queue.waitForCompletion();
// 4. 关闭数据库连接
db_manager.close();
// 5. 清理缓存
cache.clear();
// 6. 写入关闭日志
logger.log("DataStorageServer 正常关闭");
}
默认析构函数的问题
~DataStorageServer() = default; // 危险!
// 如果成员是unique_ptr,会自动删除
// 但如果成员是原始指针,会内存泄漏
// 如果线程还在运行,程序会崩溃
// 如果数据库连接未关闭,可能损坏数据
正确实现示例
头文件声明
// DataStorageServer.h
class DataStorageServer {
public:
// 构造函数
DataStorageServer(const std::string& db_path,
const std::string& storage_path);
// 析构函数(必须自定义)
~DataStorageServer();
// 禁用拷贝(避免多个对象管理同一资源)
DataStorageServer(const DataStorageServer&) = delete;
DataStorageServer& operator=(const DataStorageServer&) = delete;
// 允许移动(C++11特性)
DataStorageServer(DataStorageServer&&) = default;
DataStorageServer& operator=(DataStorageServer&&) = default;
private:
std::unique_ptr<DatabaseManager> db_manager;
std::unique_ptr<TaskQueue> task_queue;
std::unique_ptr<LRUCache<std::string, std::string>> cache;
// ...
};
源文件实现
// DataStorageServer.cpp
DataStorageServer::DataStorageServer(const std::string& db_path,
const std::string& storage_path) {
// 1. 初始化日志
logger = std::make_unique<Logger>("DataStorageServer");
// 2. 创建数据库管理器
db_manager = std::make_unique<DatabaseManager>(db_path);
// 3. 创建任务队列
task_queue = std::make_unique<TaskQueue>(*db_manager, 4);
// 4. 创建缓存
cache = std::make_unique<LRUCache<std::string, std::string>>(10000);
// 5. 创建存储引擎
storage_engine = std::make_unique<StorageEngine>(*db_manager, storage_path);
// 6. 注册任务处理器
registerTaskHandlers();
// 7. 启动监控线程
startMonitoring();
logger->log("DataStorageServer 初始化完成");
}
DataStorageServer::~DataStorageServer() {
logger->log("开始关闭DataStorageServer...");
// 1. 停止接收新请求
running = false;
// 2. 停止监控线程
stopMonitoring();
// 3. 停止任务队列(等待当前任务完成)
if (task_queue) {
task_queue->stop();
task_queue->waitForCompletion();
}
// 4. 清理缓存
if (cache) {
cache->clear();
}
// 5. 关闭数据库连接
if (db_manager) {
db_manager->close();
}
logger->log("DataStorageServer 已关闭");
}
重要设计原则
1. RAII原则
RAII 是 R esource A cquisition I s Initialization(资源获取即初始化)的缩写。
RAII 是 C++ 的核心编程范式,用于自动资源管理,核心思想是:
对象的生命周期与资源的生命周期严格绑定。
资源获取即初始化:构造函数获取资源,析构函数释放资源。
// 正确的RAII
{
DataStorageServer server(...); // 构造时获取所有资源
// 使用server...
} // 析构时自动释放所有资源
**2. 异常安全--**构造函数应该处理异常
构造函数应该处理异常,避免部分构造的对象。
DataStorageServer::DataStorageServer(...) {
try {
// 按顺序初始化
init1(); // 如果失败,已初始化的会清理
init2();
init3();
} catch (...) {
cleanupPartiallyInitialized(); // 清理已分配的资源
throw; // 重新抛出异常
}
}
3. 移动语义支持
如果对象管理大量资源,应该支持移动。
// 移动构造函数
DataStorageServer::DataStorageServer(DataStorageServer&& other) noexcept
: db_manager(std::move(other.db_manager))
, task_queue(std::move(other.task_queue))
, cache(std::move(other.cache)) {
// 转移所有权,而不是复制
}
左值和右值
这是一个C++中极其核心且重要 的概念。简单来说,它们定义了表达式的两种不同"属性",决定了这个表达式能用在什么地方。
1. 最直观的定义(经典但不完全准确)
-
左值 (lvalue) :有名字、有明确内存地址 的表达式。你可以把它想象成一个"容器"或"变量",可以放在赋值操作符的左边。
-
右值 (rvalue) :临时的、没有持久内存地址、生命周期即将结束 的表达式。通常只能放在赋值操作符的右边,作为"值"的来源。
经典例子:
int a = 10; // a 是左值,10 是右值
int b = a; // b 是左值,a 在这里是右值(虽然a是变量,但作为"值"的来源)
a = b + 5; // a 是左值,(b + 5) 这个表达式的结果是一个右值
更关键的特征:
| 特征 | 左值 (lvalue) | 右值 (rvalue) |
|---|---|---|
| **身份 (Identity)** | 有 。可以取地址 (&a)。 |
通常没有 。不能对 (b+5)取地址 (&(b+5)错误)。 |
| **可移动性 (Movable)** | 可以拷贝,也可以被移动(如果允许)。 | 通常只可移动,因为即将销毁,其资源可以被"转移"。 |
| 生命周期 | 持续到其作用域结束。 | 短暂的、临时的,通常是表达式求值后即销毁。 |
| 常见形式 | 变量名、函数名、返回左值引用的函数调用、解引用指针 (*ptr)。 |
字面量(10, "hello")、临时对象、返回非引用类型的函数调用、算术表达式结果。 |