Program
write rule of five
big project structure
在ConfigManager.h 里面
#pragma once
#include <memory>
#include <string>
#include <optional>
// C++17 Best Practice: only include what you need
class ConfigManager {
public:
ConfigManager();
~ConfigManager();
// 禁止拷贝,但允许移动
ConfigManager(const ConfigManager&) = delete;
ConfigManager& operator=(const ConfigManager&) = delete;
ConfigManager(ConfigManager&&) noexcept;
ConfigManager& operator=(ConfigManager&&) noexcept;
// 对外接口
bool load(const std::string& filename);
std::optional<std::string> get(const std::string& key) const;
private:
struct Impl; // 前置声明
std::unique_ptr<Impl> pimpl_; // PIMPL 指针
};
在实现里面 ConfigManager.cpp
#include "ConfigManager.h"
#include <fstream>
#include <map>
#include <iostream>
#include <nlohmann/json.hpp> // heavy dependency: only in .cpp
using json = nlohmann::json;
// 内部实现细节(不暴露)
struct ConfigManager::Impl {
std::map<std::string, std::string> data;
};
ConfigManager::ConfigManager()
: pimpl_(std::make_unique<Impl>()) {}
ConfigManager::~ConfigManager() = default;
// 移动构造 & 赋值
ConfigManager::ConfigManager(ConfigManager&&) noexcept = default;
ConfigManager& ConfigManager::operator=(ConfigManager&&) noexcept = default;
bool ConfigManager::load(const std::string& filename) {
std::ifstream in(filename);
if (!in.is_open()) return false;
json j;
in >> j;
for (auto& [k, v] : j.items()) {
pimpl_->data[k] = v.get<std::string>();
}
return true;
}
std::optional<std::string> ConfigManager::get(const std::string& key) const {
auto it = pimpl_->data.find(key);
if (it == pimpl_->data.end())
return std::nullopt;
return it->second;
}
工程目录结构
project/
├─ CMakeLists.txt
├─ include/ # 对外可见的公共头文件(安装/被其他目标使用)
│ └─ myproj/
│ ├─ core/
│ │ ├─ logger.h
│ │ └─ config_manager.h
│ ├─ net/
│ │ ├─ http_client.h
│ │ └─ socket.h
│ └─ util/
│ └─ string_util.h
├─ src/ # 实现 (.cpp) + 私有头(仅本目标使用)
│ ├─ core/
│ │ ├─ logger.cpp
│ │ ├─ config_manager.cpp
│ │ └─ config_manager_impl.h # 私有头,不放到 include/
│ ├─ net/
│ │ ├─ http_client.cpp
│ │ └─ socket.cpp
│ └─ util/
│ └─ string_util.cpp
├─ tests/
│ ├─ CMakeLists.txt
│ └─ core/ net/ util/ ...
├─ examples/
│ └─ minimal.cpp
└─ third_party/ # 外部依赖(如 header-only 库)
PIMPL
Pointer to IMPLementation “指向实现的指针”, 它是一种用来隐藏类实现细节的技巧。让用户只看到类的“接口(Interface)”,而看不到内部成员和依赖。
private:
struct Impl; // 前置声明 (incomplete type)
std::unique_ptr<Impl> pimpl_; // 指向实现
然后在cpp文件里面
include "config_manager.h"
#include <map>
#include <fstream>
#include <nlohmann/json.hpp>
struct ConfigManager::Impl {
std::map<std::string, std::string> data;
};
ConfigManager::ConfigManager() : pimpl_(std::make_unique<Impl>()) {}
ConfigManager::~ConfigManager() = default;
ConfigManager::ConfigManager(ConfigManager&&) noexcept = default;
ConfigManager& ConfigManager::operator=(ConfigManager&&) noexcept = default;
工程题
设计一个简单的 Logger 类
要求:
- 支持多级日志(INFO / WARN / ERROR)。
- 可以写入文件。
- 支持线程安全。
定义一个枚举类型来区分日志级别 LogLevel
Logger: 使用 线程安全懒汉式单例。通过
getInstance()获取唯一实例。禁止拷贝构造与赋值在构造函数中打开一个文件流(
std::ofstream)。每次写日志时,写入[时间][级别][内容]。可配置是否同时输出到std::cout多线程写日志可能并发访问输出流,必须加锁。
使用
std::chrono获取当前时间。使用std::put_time(来自<iomanip>) 格式化时间或从配置文件读取参数(结合前面提到的
ConfigParser)
实现一个 SmartPointer
- 模拟
std::shared_ptr的功能(引用计数)。 - 支持拷贝和移动语义。
- 析构时自动释放
实现一个 Timer 类
要求:
- 在后台线程中定时执行一个回调函数。
- 可以启动 / 停止 / 重复定时任务。
设计一个简单的 ConfigParser
要求:
- 从文本文件(例如
.ini)读取键值对配置。 - 提供
get<T>(key)接口,支持类型转换。
实现一个简单的内存池 MemoryPool
要求:
- 预分配固定大小的内存块。
- 提供
allocate/deallocate。 - 支持多线程安全可选。
简易 ThreadPool
要求:
- 支持提交任务(lambda / function)。
- 自动调度多个 worker 线程执行。
- 支持优雅关闭。
实现一个 CircularBuffer(环形缓冲区)
要求:
- 固定容量。
- 支持多线程生产者/消费者。
- 提供
push/pop。
设计一个资源管理类(如 FileHandle)
要求:
- 打开文件、读写、关闭。
- 支持异常安全。
- 不允许资源泄漏。
实现一个轻量事件系统
要求:
- 注册回调函数(监听器)。
- 触发事件时调用所有监听器。
- 支持添加/移除监听。
设计一个简单的性能计时器 ScopedTimer
要求:
- 构造时记录时间,析构时输出耗时。
- 可用于代码块性能测试。
题目示例:实现一个简易任务调度器
- 支持注册任务(函数 + 延迟时间)
- 支持周期任务
- 后台线程定时执行
- 支持线程安全关闭