在 C++ 开发中,异常处理是构建健壮应用程序的基石。想象一下你正在开发一个银行转账系统,当账户余额不足时,简单的返回-1或者false显然无法满足需求——调用方可能忽略这个错误,导致资金异常流动。这就是异常处理机制存在的意义。
C++ 通过 try-catch-throw 三件套实现异常处理:
cpp复制try {
// 可能抛出异常的代码块
riskyOperation();
}
catch (const SpecificException& e) {
// 处理特定类型异常
std::cerr << "捕获异常: " << e.what();
}
catch (...) {
// 捕获所有未处理的异常
std::cerr << "未知异常发生";
}
关键点解析:
throw 用于抛出异常对象,通常继承自 std::exceptiontry 块包含可能抛出异常的代码catch 按顺序匹配异常类型,推荐按引用捕获(const &)实际开发经验:在大型项目中,通常会在main函数最外层包裹一个try-catch块,确保任何未捕获的异常都能被记录,而不是直接崩溃。
C++标准库提供了一套完整的异常类层次结构:
code复制std::exception
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::domain_error
│ └── std::out_of_range
└── std::runtime_error
├── std::range_error
├── std::overflow_error
└── std::underflow_error
选择适当的异常类型能让代码更清晰:
对于领域特定的错误,建议创建自定义异常类:
cpp复制class DatabaseException : public std::runtime_error {
public:
enum ErrorCode {
CONNECTION_FAILED,
QUERY_TIMEOUT,
INVALID_RESULT
};
DatabaseException(ErrorCode code, const std::string& msg)
: runtime_error(msg), errorCode(code) {}
ErrorCode getErrorCode() const { return errorCode; }
private:
ErrorCode errorCode;
};
// 使用示例
throw DatabaseException(DatabaseException::CONNECTION_FAILED,
"无法连接数据库服务器");
异常安全通常分为三个等级:
RAII(Resource Acquisition Is Initialization)是C++资源管理的核心范式:
cpp复制class FileHandle {
public:
explicit FileHandle(const char* filename)
: handle(fopen(filename, "r")) {
if (!handle) throw std::runtime_error("文件打开失败");
}
~FileHandle() {
if (handle) fclose(handle);
}
// 禁用拷贝
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// 允许移动
FileHandle(FileHandle&& other) noexcept
: handle(other.handle) {
other.handle = nullptr;
}
private:
FILE* handle;
};
// 使用示例
void readFile() {
FileHandle f("data.txt"); // 资源获取即初始化
// 使用文件...
} // 自动调用析构函数释放资源
现代C++提供了三种智能指针:
cpp复制auto ptr = std::make_unique<int>(42);
// 编译错误:unique_ptr不可拷贝
// auto ptr2 = ptr;
cpp复制auto shared = std::make_shared<MyClass>();
auto shared2 = shared; // 合法,引用计数+1
cpp复制std::weak_ptr<MyClass> observer;
{
auto shared = std::make_shared<MyClass>();
observer = shared;
// observer.lock() 获取shared_ptr
} // shared析构后,observer.expired()==true
性能提示:make_shared比直接new+shared_ptr构造更高效,因为它在单次内存分配中同时创建控制块和对象。
规范的C++项目目录结构示例:
code复制project/
├── CMakeLists.txt
├── include/
│ └── project/
│ └── public_header.h
├── src/
│ ├── private_impl.cpp
│ └── main.cpp
├── tests/
│ └── test_cases.cpp
└── third_party/
└── external_libs/
基础CMakeLists.txt模板:
cmake复制cmake_minimum_required(VERSION 3.15)
project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# 添加可执行文件
add_executable(my_app
src/main.cpp
src/private_impl.cpp
)
# 包含目录
target_include_directories(my_app PRIVATE
include
)
# 链接库
target_link_libraries(my_app PRIVATE
Threads::Threads
Boost::filesystem
)
cmake复制option(ENABLE_DEBUG "Enable debug features" OFF)
if(ENABLE_DEBUG)
target_compile_definitions(my_app PRIVATE DEBUG_MODE=1)
endif()
cmake复制configure_file(
"${PROJECT_SOURCE_DIR}/config.h.in"
"${PROJECT_BINARY_DIR}/config.h"
)
cmake复制install(TARGETS my_app DESTINATION bin)
install(DIRECTORY include/ DESTINATION include)
跨平台安装方案:
bash复制sudo apt install libgtest-dev
cd /usr/src/gtest
sudo cmake .
sudo make
sudo cp *.a /usr/lib
bash复制vcpkg install gtest
cmake复制find_package(GTest REQUIRED)
target_link_libraries(my_tests PRIVATE GTest::GTest GTest::Main)
cpp复制TEST(TestSuiteName, TestCaseName) {
// 测试代码
EXPECT_EQ(actual, expected);
}
cpp复制class DatabaseTest : public ::testing::Test {
protected:
void SetUp() override {
db.connect("test.db");
}
void TearDown() override {
db.disconnect();
}
Database db;
};
TEST_F(DatabaseTest, InsertRecord) {
EXPECT_TRUE(db.insert("data"));
}
cpp复制class ParamTest : public ::testing::TestWithParam<int> {};
TEST_P(ParamTest, IsEven) {
EXPECT_EQ(GetParam() % 2, 0);
}
INSTANTIATE_TEST_SUITE_P(EvenNumbers, ParamTest,
::testing::Values(2, 4, 6, 8));
cpp复制TEST(ExceptionTest, ThrowsCorrectly) {
EXPECT_THROW({
throw std::runtime_error("error");
}, std::runtime_error);
EXPECT_NO_THROW(safeFunction());
}
cpp复制EXPECT_DOUBLE_EQ(a, b); // 精确比较
EXPECT_NEAR(a, b, 0.001); // 允许误差
cpp复制TEST(SegfaultTest, DiesCorrectly) {
EXPECT_DEATH({
int* p = nullptr;
*p = 42;
}, "Segmentation fault");
}
完整项目结构:
code复制grade_system/
├── CMakeLists.txt
├── include/
│ ├── Student.h
│ └── GradeSystem.h
├── src/
│ ├── Student.cpp
│ ├── GradeSystem.cpp
│ └── main.cpp
└── tests/
├── test_student.cpp
└── test_grade.cpp
Student.h 头文件设计:
cpp复制#pragma once
#include <string>
#include <stdexcept>
class Student {
public:
explicit Student(std::string name, double score);
const std::string& getName() const noexcept;
double getScore() const noexcept;
void updateScore(double newScore);
private:
std::string name_;
double score_;
static bool isValidScore(double score) noexcept;
};
Student.cpp 实现细节:
cpp复制#include "Student.h"
#include <algorithm>
Student::Student(std::string name, double score)
: name_(std::move(name)) {
if (!isValidScore(score)) {
throw std::out_of_range("分数必须在0-100之间");
}
score_ = score;
}
void Student::updateScore(double newScore) {
if (!isValidScore(newScore)) {
throw std::out_of_range("无效分数值");
}
score_ = newScore;
}
bool Student::isValidScore(double score) noexcept {
return score >= 0.0 && score <= 100.0;
}
测试用例示例:
cpp复制#include "Student.h"
#include <gtest/gtest.h>
TEST(StudentTest, ConstructorValidatesScore) {
EXPECT_NO_THROW(Student("Alice", 80.5));
EXPECT_THROW(Student("Bob", -10), std::out_of_range);
EXPECT_THROW(Student("Charlie", 150), std::out_of_range);
}
TEST(StudentTest, ScoreUpdateMaintainsInvariant) {
Student s("Dave", 75.0);
EXPECT_NO_THROW(s.updateScore(85.0));
EXPECT_THROW(s.updateScore(-5), std::out_of_range);
EXPECT_EQ(s.getScore(), 85.0);
}
GitHub Actions 配置 (.github/workflows/build.yml):
yaml复制name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
sudo apt update
sudo apt install -y build-essential cmake libgtest-dev
- name: Configure and build
run: |
mkdir build
cd build
cmake ..
make
- name: Run tests
run: |
cd build
ctest --output-on-failure
在CMake中集成clang-tidy:
cmake复制find_program(CLANG_TIDY_EXE "clang-tidy")
if(CLANG_TIDY_EXE)
set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE}"
"-checks=*,-modernize-use-trailing-return-type")
endif()
使用lcov生成覆盖率报告:
cmake复制if(CMAKE_BUILD_TYPE STREQUAL "Coverage")
find_program(LCOV_EXE "lcov")
find_program(GENHTML_EXE "genhtml")
add_custom_target(coverage
COMMAND ${LCOV_EXE} --directory . --capture --output-file coverage.info
COMMAND ${LCOV_EXE} --remove coverage.info '*/tests/*' '/usr/*' --output-file coverage.filtered.info
COMMAND ${GENHTML_EXE} coverage.filtered.info --output-directory coverage_report
DEPENDS run_tests
)
endif()
在CMake中启用gprof支持:
cmake复制option(ENABLE_PROFILING "Enable gprof profiling" OFF)
if(ENABLE_PROFILING)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
endif()
cpp复制#include <filesystem>
namespace fs = std::filesystem;
fs::path dataFile = fs::current_path() / "data" / "input.txt";
if (!fs::exists(dataFile)) {
throw std::runtime_error("数据文件不存在");
}
cpp复制std::string normalizeNewlines(const std::string& input) {
std::string output;
output.reserve(input.size());
for (char c : input) {
if (c != '\r') {
output += c;
}
}
return output;
}
cpp复制#ifdef _WIN32
// Windows特定实现
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
return sysInfo.dwNumberOfProcessors;
#else
// Unix-like系统实现
return sysconf(_SC_NPROCESSORS_ONLN);
#endif
cpp复制try {
// ...
} catch (...) {
// 糟糕:吞掉所有异常
}
cpp复制catch (const std::exception& e) {
logError(e.what());
throw; // 重新抛出没问题
// throw e; // 错误:切片问题
throw MyException(e); // 正确:包装原始异常
}
cpp复制void unsafeFunction() {
auto* resource = new Resource; // 可能泄漏
operationThatMayThrow();
delete resource; // 可能不会执行
}
cmake复制# 不推荐
include_directories(include)
add_definitions(-DDEBUG)
# 推荐
target_include_directories(my_target PRIVATE include)
target_compile_definitions(my_target PRIVATE DEBUG)
cmake复制# 不推荐(新增文件不会自动重新生成)
file(GLOB SOURCES "src/*.cpp")
# 推荐(显式列出所有源文件)
set(SOURCES
src/main.cpp
src/util.cpp
)
cmake复制# 错误:可能导致循环依赖
target_link_libraries(A PRIVATE B)
target_link_libraries(B PRIVATE A)
# 正确:重构代码消除循环依赖
cpp复制// 错误:测试顺序影响结果
static int counter = 0;
TEST(FlakyTest, Increment) {
++counter;
EXPECT_EQ(counter, 1);
}
TEST(FlakyTest, Check) {
EXPECT_EQ(counter, 2); // 依赖前一个测试
}
cpp复制TEST(TimingTest, FastEnough) {
auto start = std::chrono::high_resolution_clock::now();
doWork();
auto end = std::chrono::high_resolution_clock::now();
// 可能因系统负载失败
EXPECT_LT(end - start, 100ms);
}
cpp复制TEST(OverMockedTest, Useless) {
MockDatabase db;
EXPECT_CALL(db, query(_)).WillRepeatedly(Return("test"));
Processor p(db);
// 只是测试mock行为,没有实际价值
EXPECT_EQ(p.process(), "test");
}
合理使用noexcept的场景:
cpp复制class Vector {
public:
// 移动构造函数通常标记为noexcept
Vector(Vector&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
}
// 简单getter标记为noexcept
size_t getSize() const noexcept { return size; }
// 可能抛出异常的操作不标记
void push_back(const T& value);
};
适合使用错误码的场景:
错误码最佳实践:
cpp复制enum class [[nodiscard]] ErrorCode {
Success,
InvalidInput,
ResourceExhausted,
NetworkError
};
ErrorCode safeOperation(int param) noexcept {
if (param < 0) return ErrorCode::InvalidInput;
// ...
return ErrorCode::Success;
}
cpp复制class ResourceOwner {
public:
ResourceOwner() : res(createResource()) {}
~ResourceOwner() { releaseResource(res); }
// 禁用拷贝
ResourceOwner(const ResourceOwner&) = delete;
ResourceOwner& operator=(const ResourceOwner&) = delete;
// 允许移动
ResourceOwner(ResourceOwner&& other) noexcept
: res(other.res) {
other.res = nullptr;
}
private:
Resource* res;
};
cpp复制template <typename T>
class CheckedVector {
public:
T& at(size_t index) {
if (index >= size_) throw std::out_of_range("索引越界");
return data_[index];
}
private:
T* data_;
size_t size_;
};
cpp复制class ThreadSafeQueue {
public:
void push(int value) {
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(value);
}
bool try_pop(int& value) {
std::lock_guard<std::mutex> lock(mutex_);
if (queue_.empty()) return false;
value = queue_.front();
queue_.pop();
return true;
}
private:
std::queue<int> queue_;
mutable std::mutex mutex_;
};
code复制project/
├── core/
│ ├── CMakeLists.txt
│ ├── include/
│ └── src/
├── network/
│ ├── CMakeLists.txt
│ ├── include/
│ └── src/
└── app/
├── CMakeLists.txt
└── src/
cpp复制// network/api.h - 稳定接口
class NetworkClient {
public:
virtual ~NetworkClient() = default;
virtual Response sendRequest(const Request&) = 0;
// 工厂函数
static std::unique_ptr<NetworkClient> create();
};
// 实现隐藏在源文件中
std::unique_ptr<NetworkClient> NetworkClient::create() {
return std::make_unique<NetworkClientImpl>();
}
cmake复制# 使用vcpkg
find_package(Boost REQUIRED COMPONENTS filesystem system)
target_link_libraries(my_app PRIVATE Boost::filesystem)
# 使用Conan
find_package(ZLIB REQUIRED)
target_link_libraries(my_app PRIVATE ZLIB::ZLIB)
bash复制git submodule add https://github.com/nlohmann/json.git third_party/json
对应的CMake配置:
cmake复制add_subdirectory(third_party/json)
target_link_libraries(my_app PRIVATE nlohmann_json::nlohmann_json)
yaml复制jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
compiler: [gcc, clang]
exclude:
- os: macos-latest
compiler: gcc
steps:
- uses: actions/checkout@v2
- name: Build
run: |
mkdir build
cd build
cmake .. -DCMAKE_CXX_COMPILER=${{ matrix.compiler }}
make
yaml复制- name: Run clang-tidy
run: |
cd build
cmake --build . --target clang-tidy
yaml复制- name: Upload coverage
uses: codecov/codecov-action@v1
with:
file: build/coverage.info
推荐spdlog基本配置:
cpp复制#include <spdlog/spdlog.h>
#include <spdlog/sinks/rotating_file_sink.h>
void initLogger() {
try {
auto logger = spdlog::rotating_logger_mt("app",
"logs/app.log", 1048576 * 5, 3);
spdlog::set_default_logger(logger);
spdlog::set_level(spdlog::level::info);
// 异常处理中记录日志
std::set_terminate([]{
spdlog::critical("未捕获异常终止程序");
std::abort();
});
} catch (const spdlog::spdlog_ex& ex) {
std::cerr << "日志初始化失败: " << ex.what();
}
}
使用JSON配置示例:
cpp复制#include <nlohmann/json.hpp>
#include <fstream>
struct AppConfig {
int port;
std::string logLevel;
double timeout;
};
AppConfig loadConfig(const std::string& path) {
std::ifstream file(path);
if (!file) {
throw std::runtime_error("无法打开配置文件");
}
try {
nlohmann::json j;
file >> j;
return {
j.at("port").get<int>(),
j.at("log_level").get<std::string>(),
j.value("timeout", 5.0) // 默认值
};
} catch (const nlohmann::json::exception& e) {
throw std::runtime_error("配置解析错误: " + std::string(e.what()));
}
}
基本性能计数器实现:
cpp复制class PerformanceTracker {
public:
void start(const std::string& name) {
timers_[name] = Clock::now();
}
double stop(const std::string& name) {
auto it = timers_.find(name);
if (it == timers_.end()) {
throw std::invalid_argument("未知计时器");
}
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
Clock::now() - it->second);
timers_.erase(it);
return duration.count();
}
private:
using Clock = std::chrono::high_resolution_clock;
std::unordered_map<std::string, Clock::time_point> timers_;
};
cmake复制# CMakeLists.txt
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/version.h.in"
"${CMAKE_CURRENT_BINARY_DIR}/version.h"
)
version.h.in 内容:
cpp复制#pragma once
#define APP_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
#define APP_VERSION_MINOR @PROJECT_VERSION_MINOR@
#define APP_VERSION_PATCH @PROJECT_VERSION_PATCH@
#define APP_VERSION_STRING "@PROJECT_VERSION@"
cpp复制class HealthMonitor {
public:
enum class Status {
Ok,
Warning,
Critical
};
Status check() const {
if (!databaseConnected()) return Status::Critical;
if (memoryUsage() > 90) return Status::Warning;
return Status::Ok;
}
};
cpp复制std::atomic<bool> running{true};
void signalHandler(int) {
running = false;
}
int main() {
std::signal(SIGINT, signalHandler);
std::signal(SIGTERM, signalHandler);
while (running) {
// 主循环
}
// 清理资源
return 0;
}