C++ Practical Projects
Overview
Learning C++ through practical projects is the most effective way. This chapter demonstrates how to apply C++ knowledge comprehensively to actual development through several complete practical projects, including project architecture design, code organization, testing, and other development processes.
🔧 Project 1: File Management Tool
Project Architecture
cpp
// file_manager.h
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <functional>
namespace file_manager {
class FileInfo {
public:
FileInfo(std::string path, size_t size, std::time_t modified_time);
const std::string& getPath() const { return path_; }
size_t getSize() const { return size_; }
std::time_t getModifiedTime() const { return modified_time_; }
std::string getExtension() const;
bool isDirectory() const;
private:
std::string path_;
size_t size_;
std::time_t modified_time_;
};
class FileFilter {
public:
virtual ~FileFilter() = default;
virtual bool matches(const FileInfo& file) const = 0;
};
class ExtensionFilter : public FileFilter {
public:
explicit ExtensionFilter(std::vector<std::string> extensions);
bool matches(const FileInfo& file) const override;
private:
std::vector<std::string> extensions_;
};
class FileSearcher {
public:
using ProgressCallback = std::function<void(const std::string&, size_t, size_t)>;
explicit FileSearcher(std::unique_ptr<FileFilter> filter = nullptr);
std::vector<FileInfo> search(const std::string& root_path,
bool recursive = true,
ProgressCallback callback = nullptr);
void setFilter(std::unique_ptr<FileFilter> filter);
private:
std::unique_ptr<FileFilter> filter_;
};
class FileOperations {
public:
struct CopyResult {
bool success;
std::string error_message;
size_t bytes_copied;
};
static CopyResult copyFile(const std::string& source, const std::string& destination);
static bool deleteFile(const std::string& path);
static bool moveFile(const std::string& source, const std::string& destination);
static std::vector<std::string> listDirectory(const std::string& path);
};
} // namespace file_managerCore Implementation
cpp
// file_manager.cpp
#include "file_manager.h"
#include <filesystem>
#include <algorithm>
#include <iostream>
namespace file_manager {
namespace fs = std::filesystem;
FileInfo::FileInfo(std::string path, size_t size, std::time_t modified_time)
: path_(std::move(path)), size_(size), modified_time_(modified_time) {}
std::string FileInfo::getExtension() const {
auto pos = path_.find_last_of('.');
return (pos != std::string::npos) ? path_.substr(pos + 1) : "";
}
bool FileInfo::isDirectory() const {
return fs::is_directory(path_);
}
ExtensionFilter::ExtensionFilter(std::vector<std::string> extensions)
: extensions_(std::move(extensions)) {
for (auto& ext : extensions_) {
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
}
}
bool ExtensionFilter::matches(const FileInfo& file) const {
if (file.isDirectory()) return true;
std::string ext = file.getExtension();
std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
return std::find(extensions_.begin(), extensions_.end(), ext) != extensions_.end();
}
FileSearcher::FileSearcher(std::unique_ptr<FileFilter> filter)
: filter_(std::move(filter)) {}
std::vector<FileInfo> FileSearcher::search(const std::string& root_path,
bool recursive,
ProgressCallback callback) {
std::vector<FileInfo> results;
if (!fs::exists(root_path) || !fs::is_directory(root_path)) {
return results;
}
try {
size_t processed = 0;
auto iterator = recursive ?
fs::recursive_directory_iterator(root_path) :
fs::directory_iterator(root_path);
for (const auto& entry : iterator) {
auto file_info = FileInfo(
entry.path().string(),
entry.is_regular_file() ? fs::file_size(entry) : 0,
std::chrono::duration_cast<std::chrono::seconds>(
fs::last_write_time(entry).time_since_epoch()).count()
);
if (!filter_ || filter_->matches(file_info)) {
results.push_back(std::move(file_info));
}
if (callback) {
callback(entry.path().string(), ++processed, 0);
}
}
} catch (const fs::filesystem_error& e) {
std::cerr << "Filesystem error: " << e.what() << std::endl;
}
return results;
}
FileOperations::CopyResult FileOperations::copyFile(const std::string& source,
const std::string& destination) {
try {
auto bytes_copied = fs::file_size(source);
fs::copy_file(source, destination, fs::copy_options::overwrite_existing);
return {true, "", bytes_copied};
} catch (const fs::filesystem_error& e) {
return {false, e.what(), 0};
}
}
bool FileOperations::deleteFile(const std::string& path) {
try {
return fs::remove(path);
} catch (const fs::filesystem_error&) {
return false;
}
}
std::vector<std::string> FileOperations::listDirectory(const std::string& path) {
std::vector<std::string> files;
try {
for (const auto& entry : fs::directory_iterator(path)) {
files.push_back(entry.path().filename().string());
}
} catch (const fs::filesystem_error&) {
// Ignore errors
}
return files;
}
} // namespace file_manager
// Usage example
int main() {
using namespace file_manager;
// Create extension filter
auto filter = std::make_unique<ExtensionFilter>(std::vector<std::string>{"cpp", "h", "hpp"});
// Create searcher
FileSearcher searcher(std::move(filter));
// Search files
auto results = searcher.search("/path/to/search", true,
[](const std::string& file, size_t processed, size_t total) {
std::cout << "Processing: " << file << std::endl;
});
std::cout << "Found " << results.size() << " files" << std::endl;
// Display results
for (const auto& file : results) {
std::cout << file.getPath() << " (" << file.getSize() << " bytes)" << std::endl;
}
return 0;
}🌐 Project 2: Simple HTTP Server
HTTP Server Design
cpp
// http_server.h
#pragma once
#include <string>
#include <unordered_map>
#include <functional>
#include <vector>
namespace http_server {
struct HttpRequest {
std::string method;
std::string path;
std::unordered_map<std::string, std::string> headers;
std::string body;
std::unordered_map<std::string, std::string> query_params;
};
struct HttpResponse {
int status_code = 200;
std::string status_message = "OK";
std::unordered_map<std::string, std::string> headers;
std::string body;
void setHeader(const std::string& key, const std::string& value) {
headers[key] = value;
}
void setJsonResponse(const std::string& json) {
setHeader("Content-Type", "application/json");
body = json;
}
};
using RequestHandler = std::function<void(const HttpRequest&, HttpResponse&)>;
class HttpServer {
public:
explicit HttpServer(int port);
~HttpServer();
void addRoute(const std::string& method, const std::string& path, RequestHandler handler);
void start();
void stop();
private:
int port_;
int server_socket_;
bool running_;
struct Route {
std::string method;
std::string path;
RequestHandler handler;
};
std::vector<Route> routes_;
void acceptConnections();
void handleClient(int client_socket);
HttpRequest parseRequest(const std::string& raw_request);
std::string generateResponse(const HttpResponse& response);
bool routeRequest(const HttpRequest& request, HttpResponse& response);
};
} // namespace http_serverHTTP Server Implementation
cpp
// http_server.cpp
#include "http_server.h"
#include <iostream>
#include <sstream>
#include <thread>
#ifdef _WIN32
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#endif
namespace http_server {
HttpServer::HttpServer(int port) : port_(port), server_socket_(-1), running_(false) {
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(MAKEWORD(2, 2), &wsa_data);
#endif
}
HttpServer::~HttpServer() {
stop();
#ifdef _WIN32
WSACleanup();
#endif
}
void HttpServer::addRoute(const std::string& method, const std::string& path, RequestHandler handler) {
routes_.push_back({method, path, std::move(handler)});
}
void HttpServer::start() {
server_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket_ < 0) {
throw std::runtime_error("Failed to create socket");
}
sockaddr_in server_addr{};
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(port_);
if (bind(server_socket_, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
throw std::runtime_error("Failed to bind socket");
}
if (listen(server_socket_, 10) < 0) {
throw std::runtime_error("Failed to listen on socket");
}
running_ = true;
std::cout << "HTTP server started on port " << port_ << std::endl;
acceptConnections();
}
void HttpServer::stop() {
running_ = false;
if (server_socket_ >= 0) {
#ifdef _WIN32
closesocket(server_socket_);
#else
close(server_socket_);
#endif
server_socket_ = -1;
}
}
void HttpServer::acceptConnections() {
while (running_) {
sockaddr_in client_addr{};
socklen_t client_len = sizeof(client_addr);
int client_socket = accept(server_socket_, (sockaddr*)&client_addr, &client_len);
if (client_socket >= 0) {
std::thread([this, client_socket]() {
handleClient(client_socket);
}).detach();
}
}
}
void HttpServer::handleClient(int client_socket) {
char buffer[4096];
int bytes_received = recv(client_socket, buffer, sizeof(buffer) - 1, 0);
if (bytes_received > 0) {
buffer[bytes_received] = '\0';
try {
HttpRequest request = parseRequest(buffer);
HttpResponse response;
if (!routeRequest(request, response)) {
response.status_code = 404;
response.status_message = "Not Found";
response.body = "<h1>404 Not Found</h1>";
}
std::string response_str = generateResponse(response);
send(client_socket, response_str.c_str(), response_str.length(), 0);
} catch (const std::exception& e) {
std::string error_response = "HTTP/1.1 500 Internal Server Error\r\n\r\n";
send(client_socket, error_response.c_str(), error_response.length(), 0);
}
}
#ifdef _WIN32
closesocket(client_socket);
#else
close(client_socket);
#endif
}
HttpRequest HttpServer::parseRequest(const std::string& raw_request) {
HttpRequest request;
std::istringstream stream(raw_request);
std::string line;
// Parse request line
if (std::getline(stream, line)) {
std::istringstream request_line(line);
std::string version;
request_line >> request.method >> request.path >> version;
}
// Parse headers
while (std::getline(stream, line) && line != "\r" && !line.empty()) {
size_t colon_pos = line.find(':');
if (colon_pos != std::string::npos) {
std::string key = line.substr(0, colon_pos);
std::string value = line.substr(colon_pos + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t\r") + 1);
request.headers[key] = value;
}
}
return request;
}
std::string HttpServer::generateResponse(const HttpResponse& response) {
std::ostringstream stream;
stream << "HTTP/1.1 " << response.status_code << " " << response.status_message << "\r\n";
stream << "Content-Length: " << response.body.length() << "\r\n";
for (const auto& [key, value] : response.headers) {
stream << key << ": " << value << "\r\n";
}
stream << "\r\n" << response.body;
return stream.str();
}
bool HttpServer::routeRequest(const HttpRequest& request, HttpResponse& response) {
for (const auto& route : routes_) {
if (route.method == request.method && route.path == request.path) {
route.handler(request, response);
return true;
}
}
return false;
}
} // namespace http_server
// Usage example
int main() {
using namespace http_server;
HttpServer server(8080);
// Add routes
server.addRoute("GET", "/", [](const HttpRequest& req, HttpResponse& res) {
res.body = "<h1>Welcome to Simple HTTP Server!</h1>";
res.setHeader("Content-Type", "text/html");
});
server.addRoute("GET", "/api/hello", [](const HttpRequest& req, HttpResponse& res) {
res.setJsonResponse(R"({"message": "Hello, World!"})");
});
server.addRoute("GET", "/api/time", [](const HttpRequest& req, HttpResponse& res) {
auto now = std::chrono::system_clock::now();
auto time_t = std::chrono::system_clock::to_time_t(now);
res.setJsonResponse(R"({"current_time": ")" + std::ctime(&time_t) + "\"}");
});
try {
server.start();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}🎮 Project 3: Task Manager
Task Manager Design
cpp
// task_manager.h
#pragma once
#include <string>
#include <vector>
#include <memory>
#include <chrono>
#include <functional>
namespace task_manager {
enum class TaskStatus {
Pending,
InProgress,
Completed,
Cancelled
};
enum class TaskPriority {
Low,
Normal,
High,
Critical
};
class Task {
public:
Task(std::string title, std::string description, TaskPriority priority = TaskPriority::Normal);
// Getters
int getId() const { return id_; }
const std::string& getTitle() const { return title_; }
const std::string& getDescription() const { return description_; }
TaskStatus getStatus() const { return status_; }
TaskPriority getPriority() const { return priority_; }
std::chrono::system_clock::time_point getCreatedTime() const { return created_time_; }
std::chrono::system_clock::time_point getDueTime() const { return due_time_; }
// Setters
void setTitle(const std::string& title) { title_ = title; }
void setDescription(const std::string& description) { description_ = description; }
void setStatus(TaskStatus status) { status_ = status; }
void setPriority(TaskPriority priority) { priority_ = priority; }
void setDueTime(std::chrono::system_clock::time_point due_time) { due_time_ = due_time; }
// Status operations
void start() { status_ = TaskStatus::InProgress; }
void complete() { status_ = TaskStatus::Completed; }
void cancel() { status_ = TaskStatus::Cancelled; }
// Utility methods
bool isOverdue() const;
std::string toString() const;
private:
static int next_id_;
int id_;
std::string title_;
std::string description_;
TaskStatus status_;
TaskPriority priority_;
std::chrono::system_clock::time_point created_time_;
std::chrono::system_clock::time_point due_time_;
};
class TaskManager {
public:
using TaskPtr = std::shared_ptr<Task>;
using TaskFilter = std::function<bool(const TaskPtr&)>;
// Task operations
TaskPtr createTask(const std::string& title, const std::string& description,
TaskPriority priority = TaskPriority::Normal);
bool deleteTask(int task_id);
TaskPtr getTask(int task_id);
// Query and filter
std::vector<TaskPtr> getAllTasks() const;
std::vector<TaskPtr> getTasksByStatus(TaskStatus status) const;
std::vector<TaskPtr> getTasksByPriority(TaskPriority priority) const;
std::vector<TaskPtr> getOverdueTasks() const;
std::vector<TaskPtr> filterTasks(TaskFilter filter) const;
// Statistics
size_t getTaskCount() const { return tasks_.size(); }
size_t getTaskCountByStatus(TaskStatus status) const;
// Batch operations
void completeAllTasks();
void deleteCompletedTasks();
// Serialization
bool saveToFile(const std::string& filename) const;
bool loadFromFile(const std::string& filename);
private:
std::vector<TaskPtr> tasks_;
std::vector<TaskPtr>::iterator findTask(int task_id);
};
} // namespace task_managerTask Manager Implementation
cpp
// task_manager.cpp
#include "task_manager.h"
#include <algorithm>
#include <fstream>
#include <sstream>
#include <iostream>
#include <iomanip>
namespace task_manager {
int Task::next_id_ = 1;
Task::Task(std::string title, std::string description, TaskPriority priority)
: id_(next_id_++), title_(std::move(title)), description_(std::move(description)),
status_(TaskStatus::Pending), priority_(priority),
created_time_(std::chrono::system_clock::now()),
due_time_(std::chrono::system_clock::now() + std::chrono::hours(24)) {}
bool Task::isOverdue() const {
return std::chrono::system_clock::now() > due_time_ && status_ != TaskStatus::Completed;
}
std::string Task::toString() const {
std::ostringstream oss;
oss << "Task " << id_ << ": " << title_ << "\n";
oss << " Description: " << description_ << "\n";
oss << " Status: ";
switch (status_) {
case TaskStatus::Pending: oss << "Pending"; break;
case TaskStatus::InProgress: oss << "In Progress"; break;
case TaskStatus::Completed: oss << "Completed"; break;
case TaskStatus::Cancelled: oss << "Cancelled"; break;
}
oss << "\n Priority: ";
switch (priority_) {
case TaskPriority::Low: oss << "Low"; break;
case TaskPriority::Normal: oss << "Normal"; break;
case TaskPriority::High: oss << "High"; break;
case TaskPriority::Critical: oss << "Critical"; break;
}
return oss.str();
}
TaskManager::TaskPtr TaskManager::createTask(const std::string& title,
const std::string& description,
TaskPriority priority) {
auto task = std::make_shared<Task>(title, description, priority);
tasks_.push_back(task);
return task;
}
bool TaskManager::deleteTask(int task_id) {
auto it = findTask(task_id);
if (it != tasks_.end()) {
tasks_.erase(it);
return true;
}
return false;
}
TaskManager::TaskPtr TaskManager::getTask(int task_id) {
auto it = findTask(task_id);
return (it != tasks_.end()) ? *it : nullptr;
}
std::vector<TaskManager::TaskPtr> TaskManager::getAllTasks() const {
return tasks_;
}
std::vector<TaskManager::TaskPtr> TaskManager::getTasksByStatus(TaskStatus status) const {
return filterTasks([status](const TaskPtr& task) {
return task->getStatus() == status;
});
}
std::vector<TaskManager::TaskPtr> TaskManager::getOverdueTasks() const {
return filterTasks([](const TaskPtr& task) {
return task->isOverdue();
});
}
std::vector<TaskManager::TaskPtr> TaskManager::filterTasks(TaskFilter filter) const {
std::vector<TaskPtr> result;
std::copy_if(tasks_.begin(), tasks_.end(), std::back_inserter(result), filter);
return result;
}
size_t TaskManager::getTaskCountByStatus(TaskStatus status) const {
return std::count_if(tasks_.begin(), tasks_.end(),
[status](const TaskPtr& task) {
return task->getStatus() == status;
});
}
void TaskManager::completeAllTasks() {
for (auto& task : tasks_) {
if (task->getStatus() != TaskStatus::Cancelled) {
task->complete();
}
}
}
void TaskManager::deleteCompletedTasks() {
tasks_.erase(
std::remove_if(tasks_.begin(), tasks_.end(),
[](const TaskPtr& task) {
return task->getStatus() == TaskStatus::Completed;
}),
tasks_.end());
}
std::vector<TaskManager::TaskPtr>::iterator TaskManager::findTask(int task_id) {
return std::find_if(tasks_.begin(), tasks_.end(),
[task_id](const TaskPtr& task) {
return task->getId() == task_id;
});
}
} // namespace task_manager
// Usage example
int main() {
using namespace task_manager;
TaskManager manager;
// Create tasks
auto task1 = manager.createTask("Learn C++", "Complete C++ tutorial", TaskPriority::High);
auto task2 = manager.createTask("Write code", "Implement file management tool", TaskPriority::Normal);
auto task3 = manager.createTask("Test", "Write unit tests", TaskPriority::Low);
// Update task status
task1->start();
task2->complete();
// Query tasks
std::cout << "All tasks:" << std::endl;
for (const auto& task : manager.getAllTasks()) {
std::cout << task->toString() << std::endl << std::endl;
}
std::cout << "In-progress tasks: " << manager.getTaskCountByStatus(TaskStatus::InProgress) << std::endl;
std::cout << "Completed tasks: " << manager.getTaskCountByStatus(TaskStatus::Completed) << std::endl;
return 0;
}Summary
Project Development Points
- Architecture design: Clear class hierarchy and separation of concerns
- Interface design: Easy-to-use and extensible APIs
- Error handling: Robust exception handling mechanisms
- Memory management: Smart pointers and RAII principles
- Code organization: Reasonable file structure and namespaces
Best Practices
- Single responsibility: Each class focuses on one functionality
- Dependency injection: Easy to test and extend
- Interface programming: Program to interfaces not implementations
- Resource management: Automated resource cleanup
- Exception safety: Ensure program robustness
Technical Features Applied
- Modern C++: auto, lambda, smart pointers
- STL algorithms: Efficient data processing
- File system: C++17 filesystem library
- Network programming: Socket API application
- Multithreading: Concurrent processing for improved performance
Extension Suggestions
- Unit testing: Use Google Test
- Logging system: Integrate spdlog
- Configuration management: JSON/YAML configuration
- Database: SQLite integration
- GUI interface: Qt or other GUI frameworks
Practical projects are the best way to verify C++ learning outcomes. Through complete project development, you can deeply understand actual applications and best practices of C++.