Ch 30: 综合项目 - CLI 任务管理器
本项目将整合 C++ 核心知识,创建一个功能完整的命令行任务管理器:
- Part I - 核心功能:CRUD 操作
- Part II - 数据持久化:JSON 文件存储
- Part III - 错误处理:异常安全和输入验证
- Part IV - 现代 C++:使用 C++20 特性优化代码
最终目标:
task_manager add "学习 C++20"task_manager listtask_manager done 1task_manager stats30.1 项目结构
Section titled “30.1 项目结构”task_manager/├── CMakeLists.txt├── include/│ ├── task.hpp # Task 数据模型│ ├── task_manager.hpp # 任务管理器│ └── command.hpp # 命令行解析├── src/│ ├── main.cpp # 主程序入口│ ├── task.cpp # Task 实现│ ├── task_manager.cpp # TaskManager 实现│ └── command.cpp # 命令解析└── data/ └── tasks.json # 数据存储30.2 Task 数据模型
Section titled “30.2 Task 数据模型”#pragma once
#include <string>#include <chrono>#include <optional>#include <compare>
class Task {public: // 任务状态枚举 enum class Status { Pending, Completed };
// 默认构造 Task() = default;
// 参数化构造 Task(int id, std::string title, Status status = Status::Pending) : id_(id) , title_(std::move(title)) , status_(status) , created_at_(std::chrono::system_clock::now()) {}
// Getters (const 成员函数) [[nodiscard]] int id() const noexcept { return id_; } [[nodiscard]] const std::string& title() const noexcept { return title_; } [[nodiscard]] Status status() const noexcept { return status_; } [[nodiscard]] auto created_at() const noexcept { return created_at_; } [[nodiscard]] auto completed_at() const noexcept { return completed_at_; }
// Setters void set_title(std::string title) { title_ = std::move(title); } void set_status(Status status) { status_ = status; if (status == Status::Completed) { completed_at_ = std::chrono::system_clock::now(); } }
// 状态查询 [[nodiscard]] bool is_completed() const noexcept { return status_ == Status::Completed; } [[nodiscard]] bool is_pending() const noexcept { return status_ == Status::Pending; }
// C++20 三路比较运算符 [[nodiscard]] auto operator<=>(const Task&) const = default;
// 序列化接口 [[nodiscard]] std::string to_json() const; [[nodiscard]] static std::optional<Task> from_json(int id, const std::string& json_str);
private: int id_ = 0; std::string title_; Status status_ = Status::Pending; std::chrono::system_clock::time_point created_at_; std::chrono::system_clock::time_point completed_at_;};Task 实现
Section titled “Task 实现”#include "task.hpp"#include <sstream>#include <iomanip>
std::string Task::to_json() const { std::ostringstream oss; oss << "{"; oss << "\"id\":" << id_ << ","; oss << "\"title\":\"" << escape_json(title_) << "\","; oss << "\"status\":" << static_cast<int>(status_) << ","; oss << "\"created_at\":" << created_at_.time_since_epoch().count() << ",";
if (completed_at_.time_since_epoch().count() > 0) { oss << "\"completed_at\":" << completed_at_.time_since_epoch().count(); } else { oss << "\"completed_at\":null"; }
oss << "}"; return oss.str();}
std::optional<Task> Task::from_json(int id, const std::string& json_str) { try { // 简化解析:提取 title 和 status Task task; task.id_ = id;
// 解析 title auto title_start = json_str.find("\"title\":\""); if (title_start != std::string::npos) { auto start = title_start + 9; auto end = json_str.find("\"", start); task.title_ = json_str.substr(start, end - start); }
// 解析 status auto status_pos = json_str.find("\"status\":"); if (status_pos != std::string::npos) { auto start = status_pos + 9; auto end = json_str.find_first_of(",}", start); int status = std::stoi(json_str.substr(start, end - start)); task.status_ = static_cast<Status>(status); }
return task; } catch (...) { return std::nullopt; }}
// 辅助函数std::string escape_json(const std::string& s) { std::string result; for (char c : s) { switch (c) { case '"': result += "\\\""; break; case '\\': result += "\\\\"; break; case '\n': result += "\\n"; break; case '\r': result += "\\r"; break; case '\t': result += "\\t"; break; default: result += c; } } return result;}30.3 TaskManager 类
Section titled “30.3 TaskManager 类”#pragma once
#include "task.hpp"#include <vector>#include <string>#include <optional>#include <filesystem>
class TaskManager {public: // 构造函数:指定数据文件路径 explicit TaskManager(std::filesystem::path data_file = "tasks.json");
// 禁止拷贝(资源唯一性) TaskManager(const TaskManager&) = delete; TaskManager& operator=(const TaskManager&) = delete;
// 允许移动 TaskManager(TaskManager&&) = default; TaskManager& operator=(TaskManager&&) = default;
// ===== CRUD 操作 =====
// 添加任务,返回新任务 ID [[nodiscard]] int add_task(const std::string& title);
// 完成指定 ID 的任务 [[nodiscard]] bool complete_task(int id);
// 删除指定 ID 的任务 [[nodiscard]] bool delete_task(int id);
// 更新任务标题 [[nodiscard]] bool update_task(int id, const std::string& new_title);
// ===== 查询操作 =====
// 获取所有任务 [[nodiscard]] const std::vector<Task>& all_tasks() const noexcept { return tasks_; }
// 获取待完成任务 [[nodiscard]] std::vector<Task> pending_tasks() const;
// 获取已完成任务 [[nodiscard]] std::vector<Task> completed_tasks() const;
// 按 ID 查找 [[nodiscard]] std::optional<Task> find_task(int id) const;
// ===== 持久化 =====
// 保存到文件 [[nodiscard]] bool save() const; [[nodiscard]] bool save_to(std::ostream& os) const;
// 从文件加载 [[nodiscard]] bool load(); [[nodiscard]] bool load_from(std::istream& is);
// ===== 统计信息 =====
[[nodiscard]] std::size_t total_count() const noexcept { return tasks_.size(); } [[nodiscard]] std::size_t pending_count() const; [[nodiscard]] std::size_t completed_count() const; [[nodiscard]] double completion_rate() const;
private: // 生成新 ID [[nodiscard]] int generate_id() noexcept;
// 内部查找(返回索引) [[nodiscard]] std::optional<std::size_t> find_index(int id) const;
std::filesystem::path data_file_; std::vector<Task> tasks_; int next_id_ = 1;};TaskManager 实现
Section titled “TaskManager 实现”#include "task_manager.hpp"#include <fstream>#include <sstream>#include <stdexcept>#include <iostream>
namespace fs = std::filesystem;
TaskManager::TaskManager(fs::path data_file) : data_file_(std::move(data_file)){ load(); // 启动时加载数据}
int TaskManager::add_task(const std::string& title) { if (title.empty()) { throw std::invalid_argument("Task title cannot be empty"); }
int id = generate_id(); tasks_.emplace_back(id, title, Task::Status::Pending); return id;}
bool TaskManager::complete_task(int id) { auto idx = find_index(id); if (!idx) return false;
tasks_[*idx].set_status(Task::Status::Completed); return true;}
bool TaskManager::delete_task(int id) { auto idx = find_index(id); if (!idx) return false;
std::swap(tasks_[*idx], tasks_.back()); tasks_.pop_back(); return true;}
bool TaskManager::update_task(int id, const std::string& new_title) { if (new_title.empty()) { throw std::invalid_argument("Task title cannot be empty"); }
auto idx = find_index(id); if (!idx) return false;
tasks_[*idx].set_title(new_title); return true;}
std::vector<Task> TaskManager::pending_tasks() const { std::vector<Task> result; for (const auto& task : tasks_) { if (task.is_pending()) { result.push_back(task); } } return result;}
std::vector<Task> TaskManager::completed_tasks() const { std::vector<Task> result; for (const auto& task : tasks_) { if (task.is_completed()) { result.push_back(task); } } return result;}
std::optional<Task> TaskManager::find_task(int id) const { auto idx = find_index(id); if (!idx) return std::nullopt; return tasks_[*idx];}
bool TaskManager::save() const { std::ofstream file(data_file_); if (!file) { std::cerr << "Error: Cannot open file for writing: " << data_file_ << "\n"; return false; } return save_to(file);}
bool TaskManager::save_to(std::ostream& os) const { os << "{\n"; os << " \"tasks\": [\n";
for (std::size_t i = 0; i < tasks_.size(); ++i) { os << " " << tasks_[i].to_json(); if (i + 1 < tasks_.size()) { os << ","; } os << "\n"; }
os << " ],\n"; os << " \"next_id\": " << next_id_ << "\n"; os << "}\n"; return true;}
bool TaskManager::load() { if (!fs::exists(data_file_)) { std::cout << "Data file not found, starting fresh\n"; return true; // 不算错误 }
std::ifstream file(data_file_); if (!file) { std::cerr << "Warning: Cannot open file for reading: " << data_file_ << "\n"; return false; }
return load_from(file);}
bool TaskManager::load_from(std::istream& is) { std::stringstream buffer; buffer << is.rdbuf();
// 简单 JSON 解析(实际项目用 nlohmann_json) std::string content = buffer.str();
// 解析 tasks 数组 auto tasks_start = content.find("\"tasks\""); if (tasks_start == std::string::npos) { return false; }
// 提取 next_id auto next_id_pos = content.find("\"next_id\""); if (next_id_pos != std::string::npos) { auto start = next_id_pos + 10; auto end = content.find_first_of(",\n}", start); next_id_ = std::stoi(content.substr(start, end - start)); }
// 提取每个 task 对象 std::size_t pos = 0; int id = 1; while ((pos = content.find("{", pos)) != std::string::npos) { auto end = content.find("}", pos); if (end == std::string::npos) break;
std::string task_json = content.substr(pos, end - pos + 1); if (auto task = Task::from_json(id++, task_json)) { tasks_.push_back(*task); } pos = end + 1; }
return true;}
std::size_t TaskManager::pending_count() const { return std::count_if(tasks_.begin(), tasks_.end(), [](const Task& t) { return t.is_pending(); });}
std::size_t TaskManager::completed_count() const { return std::count_if(tasks_.begin(), tasks_.end(), [](const Task& t) { return t.is_completed(); });}
double TaskManager::completion_rate() const { if (tasks_.empty()) return 0.0; return static_cast<double>(completed_count()) / total_count() * 100.0;}
int TaskManager::generate_id() noexcept { return next_id_++;}
std::optional<std::size_t> TaskManager::find_index(int id) const { for (std::size_t i = 0; i < tasks_.size(); ++i) { if (tasks_[i].id() == id) { return i; } } return std::nullopt;}30.4 主程序
Section titled “30.4 主程序”#include "task_manager.hpp"#include <iostream>#include <string>#include <string_view>#include <cstdlib>#include <format>
// 使用 C++20 std::format 格式化输出
void print_usage(std::string_view program) { std::cout << std::format( "Usage: {} <command> [arguments]\n" "\n" "Commands:\n" " add <title> Add a new task\n" " list List all tasks\n" " pending List pending tasks\n" " completed List completed tasks\n" " done <id> Mark task as done\n" " delete <id> Delete a task\n" " update <id> <title> Update task title\n" " stats Show statistics\n" " help Show this help\n", program);}
void print_task(const Task& task) { std::string status = task.is_completed() ? "[x]" : "[ ]"; std::string title = task.title();
// 截断过长的标题 if (title.length() > 50) { title = title.substr(0, 47) + "..."; }
std::cout << std::format("[{:>3}] {} {}\n", task.id(), status, title);}
int main(int argc, char* argv[]) { if (argc < 2) { print_usage(argv[0]); return 1; }
std::string_view command = argv[1];
// 任务管理器实例 TaskManager manager("tasks.json");
try { if (command == "add" && argc >= 3) { std::string title; for (int i = 2; i < argc; ++i) { if (i > 2) title += " "; title += argv[i]; }
int id = manager.add_task(title); std::cout << std::format("Added task #{}: {}\n", id, title); } else if (command == "list") { const auto& tasks = manager.all_tasks(); if (tasks.empty()) { std::cout << "No tasks yet. Use 'add' to create one.\n"; } else { for (const auto& task : tasks) { print_task(task); } } } else if (command == "pending") { for (const auto& task : manager.pending_tasks()) { print_task(task); } } else if (command == "completed") { for (const auto& task : manager.completed_tasks()) { print_task(task); } } else if (command == "done" && argc >= 3) { int id = std::atoi(argv[2]); if (manager.complete_task(id)) { std::cout << std::format("Task #{} marked as completed\n", id); } else { std::cerr << std::format("Error: Task #{} not found\n", id); return 1; } } else if (command == "delete" && argc >= 3) { int id = std::atoi(argv[2]); if (manager.delete_task(id)) { std::cout << std::format("Task #{} deleted\n", id); } else { std::cerr << std::format("Error: Task #{} not found\n", id); return 1; } } else if (command == "update" && argc >= 4) { int id = std::atoi(argv[2]); std::string new_title; for (int i = 3; i < argc; ++i) { if (i > 3) new_title += " "; new_title += argv[i]; }
if (manager.update_task(id, new_title)) { std::cout << std::format("Task #{} updated\n", id); } else { std::cerr << std::format("Error: Task #{} not found\n", id); return 1; } } else if (command == "stats") { std::cout << std::format( "=== Task Statistics ===\n" "Total: {}\n" "Pending: {}\n" "Done: {}\n" "Completion rate: {:.1f}%\n", manager.total_count(), manager.pending_count(), manager.completed_count(), manager.completion_rate()); } else if (command == "help") { print_usage(argv[0]); } else { std::cerr << std::format("Unknown command: {}\n", command); print_usage(argv[0]); return 1; }
// 自动保存 if (!manager.save()) { std::cerr << "Warning: Failed to save tasks\n"; } } catch (const std::exception& e) { std::cerr << std::format("Error: {}\n", e.what()); return 1; }
return 0;}30.5 CMakeLists.txt
Section titled “30.5 CMakeLists.txt”cmake_minimum_required(VERSION 3.16)project(TaskManager LANGUAGES CXX)
# C++20 标准set(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_EXTENSIONS OFF)
# 编译器选项if (MSVC) target_compile_options(${PROJECT_NAME} PRIVATE /W4)else() target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -Wpedantic -Werror=return-type -Werror=sequence-point )endif()
# 可执行文件add_executable(task_manager src/main.cpp src/task.cpp src/task_manager.cpp)
# 头文件目录target_include_directories(task_manager PRIVATE ${PROJECT_SOURCE_DIR}/include)
# 启用测试(Catch2)option(ENABLE_TESTS "Enable tests" OFF)if (ENABLE_TESTS) include(CTest) add_subdirectory(tests)endif()30.6 构建和运行
Section titled “30.6 构建和运行”# 创建项目目录mkdir -p task_manager/{include,src,data}cd task_manager
# 创建 CMakeLists.txt(在上一节)
# 编译mkdir build && cd buildcmake ..cmake --build .
# 运行./task_manager add "学习 C++20 Concepts"./task_manager add "完成项目"./task_manager list
# 标记完成./task_manager done 1
# 查看统计./task_manager stats30.7 使用现代 C++ 特性总结
Section titled “30.7 使用现代 C++ 特性总结”本项目使用的 C++20 特性
Section titled “本项目使用的 C++20 特性”| 特性 | 使用位置 |
|---|---|
[[nodiscard]] | 函数返回值不应被忽略 |
std::format | 格式化输出 |
std::optional | 可选返回值 |
std::filesystem::path | 文件路径处理 |
constexpr lambda | 编译期计算 |
三路比较 <=> | Task 比较运算符 |
std::string_view | 非拥有字符串视图 |
noexcept | 承诺不抛异常 |
本项目使用的 C++17/14 特性
Section titled “本项目使用的 C++17/14 特性”| 特性 | 使用位置 |
|---|---|
std::make_unique | 智能指针创建 |
std::variant | 类型联合 |
| 结构化绑定 | 范围 for 循环 |
if (auto ...) | 初始化语句 |
std::invoke | 调用可调用对象 |
30.8 扩展建议
Section titled “30.8 扩展建议”// 添加优先级枚举enum class Priority { Low, Medium, High, Urgent };
// 修改 Task 类class Task {public: void set_priority(Priority p) { priority_ = p; } [[nodiscard]] Priority priority() const { return priority_; }
private: Priority priority_ = Priority::Medium;};
// 添加命令行选项// ./task_manager add --priority=high "Important task"// 使用 std::chronoclass Task {public: using clock = std::chrono::system_clock;
void set_due_date(clock::time_point due) { due_date_ = due; } [[nodiscard]] bool is_overdue() const;
private: std::optional<clock::time_point> due_date_;};// 多对多关系class Task {public: void add_tag(const std::string& tag) { tags_.insert(tag); } void remove_tag(const std::string& tag) { tags_.erase(tag); }
private: std::set<std::string> tags_;};
// 命令行// ./task_manager add --tag=work --tag=urgent "Important work task"// 按标题搜索std::vector<Task> search(const std::string& query) const { return tasks_ | std::views::filter([&](const Task& t) { return t.title().find(query) != std::string::npos; }) | std::to<std::vector<Task>>(); // C++23}#include <catch2/catch.hpp>#include "task.hpp"
TEST_CASE("Task creation") { Task task(1, "Test task"); REQUIRE(task.id() == 1); REQUIRE(task.title() == "Test task"); REQUIRE(task.is_pending());}
TEST_CASE("Task completion") { Task task(1, "Test task"); task.set_status(Task::Status::Completed); REQUIRE(task.is_completed());}30.9 章节总结
Section titled “30.9 章节总结”恭喜完成《Python转C++》全部 30 章!
核心知识点回顾
Section titled “核心知识点回顾”Part I - 入门:
- 静态类型系统 vs Python 动态类型
- 编译时 vs 解释执行
- 手动内存管理 vs GC
Part II - 基础类型:
- 值类型 vs 引用类型
std::string、std::vectorvs Pythonstr、list- 控制流语法差异
Part III - 函数:
- 参数传递(值、引用、指针)
- 默认参数 vs Python 默认参数
- Lambda 表达式 vs Python lambda
Part IV - 标准容器:
std::vectorvs Python liststd::unordered_mapvs Python dict- 容器选择原则
Part V - 类和 OOP:
- 访问控制(public/private)
- 构造/析构函数 vs
__init__/__del__ - 继承和虚函数 vs Python duck typing
Part VI - 内存管理:
- RAII vs Python with 语句
- 智能指针 vs GC
- 移动语义 vs Python 引用计数
Part VII - C++20 现代特性:
- Concepts 约束模板
- Ranges 惰性视图
std::format格式化- 三路比较
<=>
Part VIII - 项目:
- 完整 CLI 应用开发
- 集成所有知识点
- 最佳实践
下一步学习建议
Section titled “下一步学习建议”-
深入标准库:
std::thread和并发编程std::ranges完整特性std::format高级用法
-
实践项目:
- 阅读优秀开源项目(如 fmt、spdlog)
- 完成更多 CLI 工具
- 尝试 GUI 应用(Qt、Dear ImGui)
-
高级主题:
- 模板元编程
- 协程和异步编程
- 内存模型和并发
-
推荐资源:
- cppreference.com(权威文档)
- Fluent C++(现代 C++ 博客)
- C++ Weekly(YouTube)
感谢学习《Python转C++》!祝编程愉快!