C++ 异常处理与泛型编程总结

✍️ Fer·📅 2026年8月12日·👁 55 次阅读
c++开发
📚 系列:现代 C++ 学习之路

一、异常处理

⭐ C++ 异常机制概述

cpp
try {
    if (denominator == 0)
        throw std::invalid_argument("Division by zero");
    result = numerator / denominator;
}
catch (const std::invalid_argument& e) {
    std::cerr << "Invalid: " << e.what() << "\n";
}
catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << "\n";
}
catch (...) {
    std::cerr << "Unknown exception\n";
}

⭐ 标准异常类层次

⭐ 自定义异常

cpp
class HttpException : public std::runtime_error {
    int status_code_;
public:
    HttpException(int code, const std::string& msg)
        : std::runtime_error(msg), status_code_(code) {}

    int StatusCode() const { return status_code_; }
};

class NotFoundException : public HttpException {
public:
    explicit NotFoundException(const std::string& resource)
        : HttpException(404, "Not found: " + resource) {}
};

// 使用
try {
    throw NotFoundException("/api/user/123");
} catch (const HttpException& e) {
    std::cerr << e.StatusCode() << ": " << e.what() << "\n";
}

noexcept 详解

cpp
// 声明函数不会抛出异常
void SafeFunc() noexcept {
    // 如果内部抛出异常 → 直接 std::terminate()
}

// 条件 noexcept
template <typename T>
void Swap(T& a, T& b) noexcept(noexcept(T(std::move(a)))) {
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}

// 检查表达式是否 noexcept
static_assert(noexcept(42 + 1));              // true
static_assert(!noexcept(std::string("hi")));   // false(可能抛 bad_alloc)

为什么 noexcept 很重要?

规则:移动构造函数、移动赋值、析构函数、swap 都应该标记 noexcept

⭐ 异常安全等级

等级名称保证示例
无保证No guarantee可能泄漏/破坏状态不推荐
基本保证Basic不泄漏资源,对象处于有效状态大多数 STL 操作
强保证Strong要么成功,要么状态不变(事务语义)vector::push_back
不抛保证Nothrow绝不抛出异常析构函数、swap
cpp
// 强异常保证示例(copy-and-swap idiom)
class Widget {
    std::vector<int> data_;
public:
    Widget& operator=(Widget other) {  // 值传递(拷贝)
        swap(*this, other);            // noexcept swap
        return *this;                  // other 析构旧资源
    }

    friend void swap(Widget& a, Widget& b) noexcept {
        using std::swap;
        swap(a.data_, b.data_);
    }
};

异常 vs 错误码

特性异常错误码
不可忽略✅ 不处理则崩溃❌ 可以忽略返回值
正常路径性能✅ 零开销需要每次检查
异常路径性能❌ 较慢(栈展开)✅ 快
代码可读性✅ 正常流程清晰嵌套 if/else
跨层传播✅ 自动传播需要层层传递
适用场景真正的异常情况可预见的错误
cpp
// C++23 std::expected(错误码的现代替代)
#include <expected>

std::expected<int, std::string> Divide(int a, int b) {
    if (b == 0) return std::unexpected("Division by zero");
    return a / b;
}

auto result = Divide(10, 0);
if (result) {
    std::cout << *result;
} else {
    std::cout << result.error();  // "Division by zero"
}

二、泛型编程(模板高级特性)

⭐ 模板的编译模型

模板在编译时实例化(不是运行时),每种类型生成独立的函数/类代码。

⭐ 函数模板的重载解析

cpp
// 通用版本
template <typename T>
void Print(T val) { std::cout << "Template: " << val << "\n"; }

// 特化版本(针对 const char*)
template <>
void Print(const char* val) { std::cout << "Specialized: " << val << "\n"; }

// 非模板重载
void Print(int val) { std::cout << "Non-template: " << val << "\n"; }

Print(42);       // Non-template(非模板优先)
Print(3.14);     // Template(匹配通用模板)
Print("hello");  // Specialized(匹配特化版本)

解析优先级:非模板重载 > 模板特化 > 通用模板

⭐ 类模板的偏特化

cpp
// 通用版本
template <typename T, typename U>
class Pair {
    T first_; U second_;
};

// 偏特化:两个类型相同
template <typename T>
class Pair<T, T> {
    T first_; T second_;
    // 可以提供不同实现
};

// 偏特化:第二个类型为指针
template <typename T, typename U>
class Pair<T, U*> {
    T first_; U* second_;
    // 可以特殊处理指针
};

Pair<int, double> p1;    // 通用版本
Pair<int, int> p2;       // 偏特化版本(T==T)
Pair<int, double*> p3;   // 偏特化版本(U*)

type_traits 类型萃取

cpp
#include <type_traits>

// 编译期类型判断
static_assert(std::is_integral_v<int>);          // true
static_assert(std::is_floating_point_v<double>);  // true
static_assert(std::is_pointer_v<int*>);           // true
static_assert(std::is_same_v<int, int32_t>);      // true

// 类型变换
using T1 = std::remove_const_t<const int>;        // int
using T2 = std::remove_reference_t<int&>;         // int
using T3 = std::add_pointer_t<int>;               // int*
using T4 = std::decay_t<const int&>;              // int

// 条件类型
using T5 = std::conditional_t<true, int, double>;  // int
using T6 = std::conditional_t<false, int, double>; // double

⭐ SFINAE 与 enable_if

cpp
// 只接受整数类型
template <typename T>
std::enable_if_t<std::is_integral_v<T>, T>
SafeAdd(T a, T b) {
    // 检测溢出
    if (a > 0 && b > std::numeric_limits<T>::max() - a)
        throw std::overflow_error("Integer overflow");
    return a + b;
}

// 只接受浮点类型
template <typename T>
std::enable_if_t<std::is_floating_point_v<T>, T>
SafeAdd(T a, T b) {
    return a + b;  // 浮点不检查溢出
}

SafeAdd(1, 2);       // 调用整数版本
SafeAdd(1.0, 2.0);   // 调用浮点版本
// SafeAdd("a", "b"); // ❌ 编译错误(SFINAE 排除)

⭐ C++20 Concepts

cpp
#include <concepts>

// 定义概念
template <typename T>
concept Printable = requires(T t) {
    { std::cout << t } -> std::same_as<std::ostream&>;
};

template <typename T>
concept Number = std::integral<T> || std::floating_point<T>;

// 使用概念约束
template <Number T>
T Add(T a, T b) { return a + b; }

// 或者用 requires 子句
template <typename T>
requires Printable<T>
void Print(const T& val) {
    std::cout << val << "\n";
}

// 最简写法(C++20 缩写模板)
void Log(Printable auto const& msg) {
    std::cout << "[LOG] " << msg << "\n";
}

Concepts vs SFINAE

特性SFINAE / enable_ifConcepts(C++20)
可读性❌ 复杂难懂✅ 清晰自然
错误信息❌ 模板错误海洋✅ 明确的约束违反提示
组合性❌ 嵌套困难&& `
推荐C++20 前使用C++20 首选

三、编译期计算

constexpr 编译期计算

cpp
// constexpr 函数:编译期或运行期都可以执行
constexpr int Factorial(int n) {
    if (n <= 1) return 1;
    return n * Factorial(n - 1);
}

constexpr int val = Factorial(10);  // 编译期计算:3628800
int arr[Factorial(5)];              // ✅ 可以用作数组大小

int runtime_n = GetInput();
int result = Factorial(runtime_n);  // 运行期计算也可以

// C++17 constexpr if
template <typename T>
auto Convert(T t) {
    if constexpr (std::is_integral_v<T>) {
        return static_cast<double>(t);
    } else if constexpr (std::is_same_v<T, std::string>) {
        return std::stod(t);
    } else {
        return t;
    }
}

constevalconstinit(C++20)

cpp
// consteval:必须在编译期执行(立即函数)
consteval int Square(int n) { return n * n; }

constexpr int a = Square(5);   // ✅ 编译期
// int b = Square(runtime_n);  // ❌ 编译错误!必须编译期

// constinit:保证变量在编译期初始化(但不是 const)
constinit int global = 42;     // 编译期初始化
// constinit int bad = GetVal(); // ❌ 不能运行期初始化

void Foo() {
    global = 100;  // ✅ 可以修改(不是 const)
}

四、序列化

⭐ C++ 中的序列化方案

方案性能可读性跨语言适用场景
Protocol Buffers⭐⭐⭐RPC、高性能通信
FlatBuffers⭐⭐⭐⭐游戏、嵌入式
JSON✅✅REST API、配置
MessagePack⭐⭐紧凑传输

JSON 序列化示例(nlohmann/json)

cpp
#include <nlohmann/json.hpp>
using json = nlohmann::json;

// 结构体序列化
struct User {
    std::string name;
    int age;
    std::vector<std::string> hobbies;
};

// 自定义序列化
void to_json(json& j, const User& u) {
    j = json{{"name", u.name}, {"age", u.age}, {"hobbies", u.hobbies}};
}

void from_json(const json& j, User& u) {
    j.at("name").get_to(u.name);
    j.at("age").get_to(u.age);
    j.at("hobbies").get_to(u.hobbies);
}

// 使用
User user{"Alice", 30, {"reading", "coding"}};
json j = user;                          // 自动序列化
std::string s = j.dump(2);              // 格式化输出

User user2 = json::parse(s).get<User>(); // 反序列化

五、C++ 反射(有限支持)

当前状态

C++ 没有原生运行时反射(不像 Java),但有以下替代方案:

cpp
// RTTI 示例
#include <typeinfo>

class Base { virtual ~Base() = default; };
class Derived : public Base {};

Base* p = new Derived();
std::cout << typeid(*p).name();        // 输出 Derived 的类型名

if (auto* d = dynamic_cast<Derived*>(p)) {
    // 安全向下转型
}

六、最佳实践总结

⭐ 异常处理最佳实践

规则说明
const 引用捕获catch (const std::exception& e)
先捕获子类派生异常在基类异常前面
析构中不抛异常标记为 noexcept
用 RAII 保证资源安全异常时自动释放
移动操作标 noexcept让 STL 使用移动而非拷贝
不要用异常做流程控制只在真正异常情况使用

⭐ 模板编程最佳实践

规则说明
模板定义放头文件保证每个翻译单元可见
使用 Concepts(C++20)替代 SFINAE,错误信息更好
constexpr 优先能编译期计算就编译期
if constexpr 替代 SFINAE更直观的条件编译
避免模板过度嵌套控制编译时间和错误信息

七、总结知识图谱

💬 评论

加载评论中...
📑 文章目录