std::minmax使用的注意事项

15 篇文章 0 订阅

预备知识

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仍然可以使用。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值