std::minmax使用的注意事项

博客探讨了C++中std::minmax函数在Debug和Release模式下行为不一致的问题。在Debug模式下,程序正确输出最大值和最小值,但在Release模式下由于临时变量引用导致错误结果。问题根源在于std::minmax返回临时变量的引用,而Release模式的优化可能导致这些临时变量未分配内存。解决方案包括将结果转换为值类型或使用std::minmax_element结合初始化列表。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

预备知识

C++11中新增加了一个函数,std::minmax, 它可以获取两个或者多个元素的最大最小值。
相比之前我们先用std::max求处最大值,再用std::min求出最小值,std::minmax的优势是可以一次遍历获得最大最小值。

问题介绍

最近在项目中遇到了一个比较诡异的问题,就是一个unittest在开发中使用的debug build进行运行时,没有问题,但是在release build进行运行时就会挂掉。debug build默认使用的是-O0, release build使用的是-O2, 最后通过debug,缩小问题的规模,发现居然是误用std::minmax造成的。
下面的程序:

#include <algorithm>
#include <iostream>

int main(int argc, const char *argv[]) {

    auto a  = std::minmax(3, 5);
    std::cout << a.first << " " << a.second << std::endl;

    return 0;
}

采用debug build的编译选项进行编译:

gcc/7.3.0_1/bin/g++ -o minmaxTest minmaxTest.cpp 

输出的结果是3和5,符合预期。

采用release build的编译选项进行编译:

gcc/7.3.0_1/bin/g++ -O2 -o minmaxTestO2 minmaxTest.cpp 

输出的结果却是0和0,这是为什么呢?

问题分析

查看使用到的标准库中的std::minmax的函数原型:

 template<typename _Tp>
    inline pair<const _Tp&, const _Tp&>
    minmax(const _Tp& __a, const _Tp& __b)
    {
      // concept requirements
      __glibcxx_function_requires(_LessThanComparableConcept<_Tp>)

      return __b < __a ? pair<const _Tp&, const _Tp&>(__b, __a)
                       : pair<const _Tp&, const _Tp&>(__a, __b);
    }

minmax返回的是最小最大值的引用: pair<const _Tp&, const _Tp&>,所以上面代码等价于:

 std::pair<const int &, const int &> p1 = std::pair<const int &, const int &>(std::minmax(3, 5));

但是3和5作为字面常量,其为临时变量,p1使用临时变量的引用,行为是未知的。

那么为什么用debug build输出的又是正确的呢?这就需要看看编译器为我们做了什么。
采用debug build时,我们可以发现,编译器事实上给3和5分配了内存地址,链接

        mov     DWORD PTR [rbp-8], 5
        mov     DWORD PTR [rbp-4], 3
        lea     rdx, [rbp-8]
        lea     rax, [rbp-4]
        mov     rsi, rdx
        mov     rdi, rax
        call    std::pair<int const&, int const&> std::minmax<int>(int const&, int const&)
        mov     QWORD PTR [rbp-32], rax
        mov     QWORD PTR [rbp-24], rdx

采用-O2时,编译器没有为3和5分配内存地址,链接

main:
        xor     eax, eax
        ret
_GLOBAL__sub_I_main:
        sub     rsp, 8
        mov     edi, OFFSET FLAT:_ZStL8__ioinit
        call    std::ios_base::Init::Init() [complete object constructor]
        mov     edx, OFFSET FLAT:__dso_handle
        mov     esi, OFFSET FLAT:_ZStL8__ioinit
        mov     edi, OFFSET FLAT:_ZNSt8ios_base4InitD1Ev
        add     rsp, 8

所以,debug build时是在编译器的帮助下碰巧正确了,release build才是程序的本来面目。

问题解决

显然,程序运行出问题不是编译器的锅,对于它在-O0时的优化,我们不能期待它也用到-O2中,编译器只是做了它改做的,编译器没有义务修复我们的问题代码。对于这个问题,我们是否有更好的解决办法呢,可以让程序不论在release build还是debug build都可以正常运行?

解决方案1

既然问题出在使用临时变量的引用上,我们可以直接使用值传递的方式来避免这个问题:

const std::pair<int, int>& a  = std::minmax(3, 5);

返回值虽然是临时变量的引用,但是强转到值类型,后面就可以正常使用了。
这是一个典型的不能使用auto的场景,因为auto推导出的正确的类型不是我们想要的类型。

解决方案2

上面的办法只是避开了临时变量的引用的问题,最根本的原因在于minmax返回了引用,那么minmax是否有不返回引用的情形呢?有的,采用初始化列表的minmax函数就返回值

    template<typename _Tp>
    inline pair<_Tp, _Tp>
    minmax(initializer_list<_Tp> __l)
    {
      pair<const _Tp*, const _Tp*> __p =
        std::minmax_element(__l.begin(), __l.end());
      return std::make_pair(*__p.first, *__p.second);
    }

我们可以把3和5放到初始化列表中去:

auto a  = std::minmax({3, 5});

这样auto仍然可以使用。

<think>我们正在讨论C++std::abs函数的使用或相关问题。根据用户之前的问题,他们可能是在处理浮点数比较(如10的-9次方)时遇到了精度问题,因此需要了解std::abs函数在浮点数比较中的应用。首先,std::abs函数用于计算一个数的绝对值。在C++中,它被重载以支持不同的数值类型(如int,long,float,double等)。在浮点数比较中,由于浮点数的精度限制,直接比较两个浮点数是否相等可能会因为舍入误差而导致错误的结果。因此,通常的做法是检查两个浮点数的差的绝对值是否小于一个很小的容差值(tolerance),这个容差值可以是10的-9次方(1e-9)等。下面我将详细说明std::abs的使用方法,并给出在浮点数比较中的应用示例。###std::abs函数的基本用法在C++中,使用std::abs需要包含头文件`<cmath>`(对于C++)或`<cstdlib>`(对于整数类型,但C++中推荐使用`<cmath>`,因为它对浮点数和整数都有重载)。```cpp#include<cmath>#include<iostream>intmain(){doublex=-1.2;doubleabs_x=std::abs(x);std::cout<<"|"<<x<<"|="<<abs_x<<std::endl;//输出:|-1.2|=1.2intn=-5;intabs_n=std::abs(n);std::cout<<"|"<<n<<"|="<<abs_n<<std::endl;//输出:|-5|=5return0;}```###在浮点数比较中的应用当我们比较两个浮点数a和b是否相等时,应该避免使用`a==b`,而应该使用:```cppif(std::abs(a-b)<tolerance){//认为a和b相等}```其中`tolerance`是一个很小的正数,例如`1e-9`(10的-9次方)。示例:```cpp#include<cmath>#include<iostream>intmain(){doublea=0.1+0.2;//由于浮点精度,a可能不等于0.3doubleb=0.3;//错误的比较if(a==b){std::cout<<"a==b"<<std::endl;}else{std::cout<<"a!=b"<<std::endl;}//正确的比较方式doubletolerance=1e-9;if(std::abs(a-b)<tolerance){std::cout<<"aisapproximatelyequaltob"<<std::endl;}else{std::cout<<"aisnotapproximatelyequaltob"<<std::endl;}return0;}```输出可能是:```a!=baisapproximatelyequaltob```###注意事项1.**容差值的选择**:容差值的选择取决于具体应用场景。对于大多数情况,`1e-9`对于`double`类型已经足够小,但如果处理的数据量级很大或很小,可能需要调整容差值。有时会使用相对容差,即`std::abs(a-b)<tolerance*std::max(std::abs(a),std::abs(b))`,以避免当数值很大时绝对容差失效。2.**重载函数**:C++标准库为不同的数值类型提供了std::abs的重载,因此它可以用于整数、浮点数等。但要注意,如果传入复数类型(如`std::complex`),则需要包含`<complex>`头文件,并使用`std::abs`,它返回复数的模。3.**C++11中的新特性**:C++11引入了`std::fabs`,但通常我们使用`std::abs`即可,因为它会通过重载自动选择正确的版本。`std::fabs`是C语言中`fabs`的C++版本,主要为了兼容性,它只用于浮点数。###相关函数-`std::fabs`:专门用于浮点数的绝对值(在C语言中,abs用于整数,fabs用于浮点数)。在C++中,使用`std::abs`通常更通用。-`std::labs`,`std::llabs`:用于`long`和`longlong`类型的绝对值。###总结在浮点数比较中,使用`std::abs`结合一个小的容差值是一种标准做法。这可以避免浮点数精度误差带来的问题。在编写科学计算或数值分析程序时,这种方法尤为重要。§§相关问题§§1.如何选择浮点数比较中的容差值?2.相对容差和绝对容差有什么区别?各自适用于什么场景?3.C++中如何处理复数(complex)的绝对值?4.在模板编程中,如何编写一个通用的绝对值函数以处理各种数值类型?</think>### C++ 中 `std::abs` 函数的使用指南 `std::abs` 是 C++ 标准库中用于计算绝对值的函数,在 `<cmath>` 头文件中定义。它在数值计算中至关重要,尤其是在浮点数比较和误差控制场景中。 #### 基础用法 ```cpp #include <cmath> #include <iostream> int main() { // 整数类型 int i = -5; std::cout << "|-5| = " << std::abs(i) << "\n"; // 输出 5 // 浮点类型 double d = -3.14; std::cout << "|-3.14| = " << std::abs(d) << "\n"; // 输出 3.14 // 科学计数法 double epsilon = 1e-9; std::cout << "|1e-9| = " << std::abs(epsilon) << "\n"; // 输出 1e-9 } ``` #### 核心应用场景 1. **浮点数精度比较** 解决浮点数直接比较的精度问题: ```cpp bool isEqual(double a, double b, double tol = 1e-9) { return std::abs(a - b) < tol; // 使用容差比较[^1] } ``` 2. **数学计算** 在向量运算、统计计算中广泛应用: ```cpp double distance(double x1, double y1, double x2, double y2) { return std::sqrt(std::abs(x1 - x2) * std::abs(x1 - x2) + std::abs(y1 - y2) * std::abs(y1 - y2)); } ``` 3. **错误处理** 验证输入值的合法性: ```cpp void processValue(double val) { if (std::abs(val) > 100.0) { throw std::runtime_error("Value out of range"); } // 处理逻辑 } ``` #### 注意事项 1. **类型安全** `std::abs` 已针对不同类型重载: ```cpp std::abs(10); // int → int std::abs(10.5f); // float → float std::abs(10.5); // double → double ``` 2. **整型溢出风险** 对最小负整数取绝对值可能导致溢出: ```cpp int minInt = INT_MIN; // 例如 -2147483648 // std::abs(minInt); // 未定义行为! ``` 3. **复数支持** 对于 `std::complex` 类型需使用专用方法: ```cpp #include <complex> std::complex<double> c(3, -4); double mag = std::abs(c); // 模长 = 5 ``` #### 性能优化 - 编译器通常将 `std::abs` 内联为 CPU 指令(如 `fabs`) - 对性能敏感场景建议直接使用编译器内置函数: ```cpp #ifdef __GNUC__ #define fast_abs(x) __builtin_fabs(x) #else #define fast_abs(x) std::abs(x) #endif ``` ### 常见问题解决 **问题:** 为什么 `std::abs(1e-9)` 有时比较失败? **原因:** 浮点舍入误差累积 **解决方案:** 使用相对误差比较: ```cpp bool isRelEqual(double a, double b, double rel_tol = 1e-9) { double max_val = std::max(std::abs(a), std::abs(b)); return std::abs(a - b) < rel_tol * max_val; } ```
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值