std::minmax使用的注意事项
Posted ithiker
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了std::minmax使用的注意事项相关的知识,希望对你有一定的参考价值。
预备知识
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仍然可以使用。
以上是关于std::minmax使用的注意事项的主要内容,如果未能解决你的问题,请参考以下文章