如果std::max()通过引用返回(这是必须的),这是否会导致悬空引用

If std::max() returns by reference (as it must), might that lead to a dangling reference?

本文关键字:引用 是否 返回 max std 如果 必须的      更新时间:2023-10-16

考虑典型的max模板函数std::max():

// From the STL
// TEMPLATE FUNCTION _Debug_lt
template<class _Ty1, class _Ty2> inline
bool _Debug_lt(const _Ty1& _Left, const _Ty2& _Right,
_Dbfile_t _File, _Dbline_t _Line)
{   // test if _Left < _Right and operator< is strict weak ordering
if (!(_Left < _Right))
return (false);
else if (_Right < _Left)
_DEBUG_ERROR2("invalid operator<", _File, _Line);
return (true);
}
// intermediate #defines/templates skipped 
// TEMPLATE FUNCTION max
template<class _Ty> inline
const _Ty& (max)(const _Ty& _Left, const _Ty& _Right)
{   // return larger of _Left and _Right
return (_DEBUG_LT(_Left, _Right) ? _Right : _Left);
}

或者,以更简单的形式:

template<typename T> inline
T const & max(T const & lhs, T const & rhs)
{
return lhs < rhs ? rhs : lhs;
}

我理解为什么max模板必须通过引用返回(以避免昂贵的副本并避免对副本构造函数的要求)。

然而,这不是导致悬挂引用的可能性吗,就像下面的代码一样?

int main()
{
const int & max_ = ::max(3, 4);
int m = max_; // Is max_ a dangling reference?
}

在这种情况下,代码构建和运行良好(VS 2010),并且值m设置为4。然而,我突然想到,max_是一个悬空的引用,因为硬编码的右值34直接传递给max,并且在到达下一行代码时,为这些硬编码的右值常数分配的存储可以被正确地取消分配。

我可以为成熟的对象设想类似的边缘情况,例如

class A
{
friend bool operator<(A const &, A const &)
{
return false;
}
};
int main()
{
const A & a_ = ::max(A(), A());
A a = a_; // Is a_ a dangling reference?
}

max的这种用法——在调用参数列表中定义的右值作为参数传递——是使用max的潜在"gotcha"的一个例子,这是不可避免的,因为max需要定义为返回引用,以避免昂贵的副本(并避免副本构造函数的要求)?

在现实生活中,出于本问题中讨论的原因,定义一个通过值而不是引用返回结果的max版本是否是一种良好的编程实践?

是的,它是一个悬空引用。

是的,我明白了。

按值返回的版本可能很有用,前提是您默认使用,并在需要的情况下切换到按引用。如果您默认使用按引用版本,并且只在需要时切换到按值版本,那么您仍然会陷入困境,因为意识到您需要它与意识到您应该编写const A a_= ::max(A(), A());是一样的。

不幸的是,按价值计算的一个引入了一个新的陷阱:

A a, b, c;
mymax(a, b) = c; // assigns to temporary, not to a or b

(事实上,看看这个代码,我想如果你把它叫做max_by_val,那么你就不会写这个了)。

您可以按const值返回,有些人曾经建议运算符重载应按const返回。但这在C++11中引入了一个性能难题:

A a, b, c;
c = constmymax(a, b); // copies from const rvalue instead of moving.

并且即使在C++03中它也阻止了等价的显式优化CCD_ 15。