You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何确保调用sort的类满足C++三/零/五法则?

背景

此背景仅用于说明问题的由来,您可直接跳至「问题」部分。

近期我们公司遇到了一些无法复现的崩溃问题。经调查发现,我们的vector实现默认使用qsort作为排序方法。崩溃的原因是qsort对非平凡可移动类会产生未定义行为。

我们修改了代码:对平凡可移动类保留qsort,对可移动构造且可移动赋值的类使用std::sort,其余类则不允许排序。但随后又出现了新的崩溃,因为部分类虽满足可移动构造和可移动赋值,却未遵循三法则。之前能正常运行是因为仅进行内存移动,但现在自动生成的移动构造函数被调用后就引发了问题。

以下是一段示例代码:仅做内存移动时运行正常,但调用移动构造函数会导致双重释放:

struct MyStruct {
    int* myPointer;

    MyStruct()
    {
        myPointer = new int;
    }

    ~MyStruct()
    {
        delete myPointer;
    }
};

这个类的写法存在问题,这是根源所在,但由于我们要修改原有行为,且代码库包含数千个文件,无法逐一检查所有sort调用。由此引出我们的问题。

问题

如何检查调用sort的类是否遵循三法则(或零法则、五法则),以确保其能正常工作?

我们曾尝试检查类是否可移动构造和可移动赋值,但这并不足够。例如上述示例结构体中,以下静态断言会通过:

static_assert(std::is_move_constructible<MyStruct>::value);
static_assert(std::is_move_assignable<MyStruct>::value);

自定义析构函数会阻止隐式生成移动构造函数,但复制构造函数仍会被生成。而若复制构造函数已定义,该类就会被视为可移动构造。


解决方案

1. 静态断言组合检查

要判断类是否符合三/零/五法则,核心逻辑是:要么所有特殊成员函数都由编译器隐式生成(零法则),要么自定义了析构函数的同时,显式定义/删除了所有复制、移动相关操作(三/五法则)。可以通过以下模板函数实现编译期检查:

零法则合规性检查

template <typename T>
constexpr bool follows_zero_rule() {
    return std::is_trivially_destructible_v<T> &&
           std::is_trivially_copy_constructible_v<T> &&
           std::is_trivially_copy_assignable_v<T> &&
           std::is_trivially_move_constructible_v<T> &&
           std::is_trivially_move_assignable_v<T>;
}

三/五法则合规性检查

template <typename T>
constexpr bool follows_three_five_rule() {
    // 自定义析构函数的类,必须保证复制/移动操作的一致性(要么都能调用,要么都被删除)
    bool has_custom_dtor = !std::is_trivially_destructible_v<T>;
    bool copy_ops_consistent = std::is_copy_constructible_v<T> == std::is_copy_assignable_v<T>;
    bool move_ops_consistent = std::is_move_constructible_v<T> == std::is_move_assignable_v<T>;

    return follows_zero_rule<T>() || (has_custom_dtor && copy_ops_consistent && move_ops_consistent);
}

将检查嵌入sort调用逻辑:

template <typename T>
void my_sort(std::vector<T>& vec) {
    static_assert(follows_three_five_rule<T>(), 
                  "Type T must follow zero/three/five rule to be sorted");
                  
    if (std::is_trivially_move_constructible_v<T> && std::is_trivially_move_assignable_v<T>) {
        qsort(vec.data(), vec.size(), sizeof(T), compare<T>);
    } else {
        std::sort(vec.begin(), vec.end());
    }
}

2. 编译与静态分析工具

  • Clang-Tidy:启用cppcoreguidelines-special-member-functions检查器,自动扫描违反三/五法则的类并发出警告,可集成到构建流程批量处理代码库。
  • 编译器警告:GCC 10+或Clang开启-Wdeprecated-copy参数,当类自定义析构但未显式处理复制操作时,编译器会触发警告。
  • 静态分析工具:使用Coverity、Cppcheck等工具深度扫描代码,识别浅拷贝、双重释放等潜在问题,弥补静态断言的覆盖盲区。

3. 运行时辅助检测

在测试环境中启用地址 sanitizer(ASAN),编译时添加参数:

g++ -fsanitize=address -O0 your_code.cpp -o your_program

ASAN会在运行时实时检测双重释放、野指针等问题,帮助快速定位违反规则的类实例。

内容的提问来源于stack exchange,提问作者Nathan Loudjani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 09:12:40