Skip to content

Ch 30: 综合项目 - CLI 任务管理器

本项目将整合 C++ 核心知识,创建一个功能完整的命令行任务管理器:

  • Part I - 核心功能:CRUD 操作
  • Part II - 数据持久化:JSON 文件存储
  • Part III - 错误处理:异常安全和输入验证
  • Part IV - 现代 C++:使用 C++20 特性优化代码

最终目标:

Terminal window
task_manager add "学习 C++20"
task_manager list
task_manager done 1
task_manager stats
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 # 数据存储
include/task.hpp
#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_;
};
src/task.cpp
#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;
}
include/task_manager.hpp
#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;
};
src/task_manager.cpp
#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;
}
src/main.cpp
#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;
}
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()
Terminal window
# 创建项目目录
mkdir -p task_manager/{include,src,data}
cd task_manager
# 创建 CMakeLists.txt(在上一节)
# 编译
mkdir build && cd build
cmake ..
cmake --build .
# 运行
./task_manager add "学习 C++20 Concepts"
./task_manager add "完成项目"
./task_manager list
# 标记完成
./task_manager done 1
# 查看统计
./task_manager stats
特性使用位置
[[nodiscard]]函数返回值不应被忽略
std::format格式化输出
std::optional可选返回值
std::filesystem::path文件路径处理
constexpr lambda编译期计算
三路比较 <=>Task 比较运算符
std::string_view非拥有字符串视图
noexcept承诺不抛异常
特性使用位置
std::make_unique智能指针创建
std::variant类型联合
结构化绑定范围 for 循环
if (auto ...)初始化语句
std::invoke调用可调用对象
// 添加优先级枚举
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::chrono
class 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
}
tests/test_task.cpp
#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());
}

恭喜完成《Python转C++》全部 30 章!

Part I - 入门:

  • 静态类型系统 vs Python 动态类型
  • 编译时 vs 解释执行
  • 手动内存管理 vs GC

Part II - 基础类型:

  • 值类型 vs 引用类型
  • std::string、std::vector vs Python str、list
  • 控制流语法差异

Part III - 函数:

  • 参数传递(值、引用、指针)
  • 默认参数 vs Python 默认参数
  • Lambda 表达式 vs Python lambda

Part IV - 标准容器:

  • std::vector vs Python list
  • std::unordered_map vs 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 应用开发
  • 集成所有知识点
  • 最佳实践
  1. 深入标准库:

    • std::thread 和并发编程
    • std::ranges 完整特性
    • std::format 高级用法
  2. 实践项目:

    • 阅读优秀开源项目(如 fmt、spdlog)
    • 完成更多 CLI 工具
    • 尝试 GUI 应用(Qt、Dear ImGui)
  3. 高级主题:

    • 模板元编程
    • 协程和异步编程
    • 内存模型和并发
  4. 推荐资源:

    • cppreference.com(权威文档)
    • Fluent C++(现代 C++ 博客)
    • C++ Weekly(YouTube)

感谢学习《Python转C++》!祝编程愉快!