有没有办法防止开发人员使用std::min, std::max

Is there a way to prevent developers from using std::min, std::max?

本文关键字:std min max 开发 有没有      更新时间:2023-10-16

我们有一个算法库,可以对可能是NaN的数字进行大量的std::min/std::max操作。考虑到这篇文章:为什么发布/调试对std::min有不同的结果?,我们意识到这显然是不安全的。

是否有办法防止开发人员使用std::min/std::max ?

我们的代码是用VS2015和g++编译的。我们有一个包含在所有源文件中的通用头文件(通过VS2015的/FI选项和g++的-include选项)。是否有任何代码/pragma可以放在这里,使使用std::minstd::max的任何cpp文件无法编译?

顺便说一下,使用此函数的STL头等遗留代码不应该受到影响。只有我们编写的代码应该受到影响。

我不认为使标准库函数不可用是正确的方法。首先,NaN是浮点值工作的一个基本方面。你需要禁用所有其他的东西,例如,sort(), lower_bound()等。此外,程序员的创造性是有报酬的,如果std::max(a, b)不起作用,我怀疑任何程序员在使用std::max()时会犹豫使用a < b? b: a

同样,你显然不想禁用std::max()std::min()的类型没有NaN,例如,整数或字符串。因此,您需要一种稍微受控制的方法。

没有可移植的方法来禁用命名空间std中的任何标准库算法。你可以通过提供合适的 deleted重载来定位这些算法的使用,例如:

namespace std {
    float       max(float, float) = delete;             // **NOT** portable
    double      max(double, double) = delete;           // **NOT** portable
    long double max(long double, long double) = delete; // **NOT** portable
    // likewise and also not portable for min
}

我将在这里使用更少的代码。但我认为最好的方法是教育这些开发人员,并解释为什么他们不应该以特定的方式编码。如果你能给他们一个很好的解释,那么他们不仅会停止使用你不希望他们使用的函数。他们将能够将消息传播给团队中的其他开发人员。

我相信强迫他们只会让他们想出变通办法。

由于修改std是不允许的,下面是UB,但可能在您的情况下工作。将函数标记为已弃用:

自c++14起,已弃用的属性:

namespace std
{
    template <typename T>
    [[deprecated("To avoid to use Nan")]] constexpr const T& (min(const T&, const T&));
    template <typename T>
    [[deprecated("To avoid to use Nan")]] constexpr const T& (max(const T&, const T&));
}

演示

#ifdef __GNUC__
# define DEPRECATED(func) func __attribute__ ((deprecated))
#elif defined(_MSC_VER)
# define DEPRECATED(func) __declspec(deprecated) func
#else
# pragma message("WARNING: You need to implement DEPRECATED for this compiler")
# define DEPRECATED(func) func
#endif
namespace std
{
    template <typename T> constexpr const T& DEPRECATED(min(const T&, const T&));
    template <typename T> constexpr const T& DEPRECATED(max(const T&, const T&));
}

Demo

没有可移植的方法,因为除了几个例外,您不允许更改std中的任何内容。

然而,一个解决方案是

#define max foo

,然后再包含任何代码。然后std::maxmax都将发出编译时失败。

但说真的,如果我是你,我会习惯std::maxstd::min在你的平台上的行为。如果它们没有按照标准的要求去做,那么就向编译器供应商提交一个bug报告。

如果在调试和发布中得到不同的结果,那么问题不是得到不同的结果。问题是其中一个版本,或者可能两个版本都错了。禁止std::min或std::max或用定义了结果的不同函数替换它们并不能解决这个问题。您必须弄清楚每个函数调用实际需要的结果是什么,以获得正确的结果。

我不打算确切地回答你的问题,但与其完全禁止std::minstd::max,你可以教育你的同事,并确保无论何时使用依赖于给定顺序的函数,你都始终使用一个总顺序比较器,而不是一个原始的operator<(许多标准库算法隐式使用)。

在P0100 - c++中的比较(以及部分和弱顺序比较器)中提出了这样一个比较器的标准化,可能针对c++ 20。同时,C标准委员会已经在TS 18661 - C的浮点扩展,第1部分:二进制浮点算术上工作了很长一段时间,显然是针对未来的C2x(应该是~C23),它更新了<math.h>头,其中包含了实现最近的ISO/IEC/IEEE 60559:2011标准所需的许多新功能。在新函数中,有totalorder(第14.8节),它根据IEEE totalOrder:

比较浮点数。

totalOrder(xy)对格式为xy:

的正则成员施加一个全排序。
  1. If x <<em> , totalOrder ( x , y )是真的。
  2. 如果x > , totalOrder ( x , y )是错误的。
  3. 如果x = y
    1. totalOrder(-0, +0)为true。
    2. totalOrder(+0, -0)为假。
    3. 如果xy表示相同的浮点基准:
      • xy有负号时,当且仅当x的指数≥y的指数,totalOrder(xy)为真。
      • 否则totalOrder(xy)当且仅当x的指数≤y的指数为真。
  4. 如果xy是无序的数字,因为xy是NaN:
    1. totalOrder(−NaN, y)为true,其中−NaN表示带负号位的NaN, y为浮点数。
    2. totalOrder(x, +NaN)为true,其中+NaN表示带正号位的NaN, x为浮点数。
    3. 如果xy都是nan,则totalOrder反映基于以下条件的总排序:
      • 正号下的负号序
      • 下面的信令顺序为+NaN静默,为−NaN反转
      • 较小的有效载荷,当被视为整数时,+NaN的有效载荷低于较大的顺序,−NaN的相反。

这是一个相当大的文本墙,所以这里有一个列表,帮助你看到什么比什么大(从大到小):

  • 正安静nan(按负载作为整数排序)
  • 正信令nan(按有效载荷作为整数排序)
  • +∞
  • 正实数
  • 积极
  • 0
  • 负0
  • 负实数
  • 负无穷
  • 负信令nan(按有效载荷作为整数排序)
  • 负安静nan(按负载作为整数排序)

不幸的是,这个总顺序目前缺乏库支持,但可能有可能为浮点数拼凑一个自定义的总顺序比较器,并在您知道要比较浮点数时使用它。一旦您掌握了这样一个总顺序比较器,您就可以安全地在任何需要的地方使用它,而不是简单地禁用std::minstd::max

如果您使用GCC或Clang进行编译,您可以毒害这些标识符。

#pragma GCC poison min max atoi /* etc ... */

使用它们会产生编译错误:

error: attempt to use poisoned "min"
在c++中唯一的问题是你只能毒害"标识符令牌",而不是std::minstd::max,所以实际上也毒害所有函数和局部变量的名称minmax…也许不完全是你想要的,但如果你选择好的描述性变量名™,也许不是问题。

如果一个有毒的标识符出现在宏的扩展中如果是在标识符被毒害之前定义的,那么将不会导致错误。这使您可以在不担心的情况下毒害标识符关于定义使用它的宏的系统头文件。

例如,

#define strrchr rindex
#pragma GCC poison rindex
strrchr(some_string, 'h');

不会产生错误。

当然,要了解更多信息,请阅读链接。

https://gcc.gnu.org/onlinedocs/gcc - 3.3 -/- cpp/pragmas.html

您已弃用std::min std::max。您可以通过使用grep进行搜索来查找实例。或者你可以摆弄头文件本身来打破std::min, std::max。或者您可以尝试向预处理器定义min/max或std::min, std::max。后者有点危险,因为c++的命名空间,如果你定义std::max/min,你不会使用命名空间std,如果你定义min/max,你还会使用这些标识符的其他用法。

或者如果项目有一个标准的头文件,比如"mylibrary. exe"。

当然,当传递NaN时,函数应该返回NaN。但是自然的写法总是会触发错误。

在我看来,c++语言标准要求min(NaN, x)和min(x, NaN)返回NaN和max的失败是c++语言标准中的一个严重缺陷,因为它隐藏了NaN已经生成的事实,并导致了令人惊讶的行为。很少有软件开发人员做足够的静态分析,以确保永远无法为所有可能的输入值生成nan。因此,我们为min和max声明了自己的模板,并对float和double进行了特化,以便使用NaN参数提供正确的行为。这适用于我们,但可能不适用于那些使用STL更大部分的人。我们的领域是高完整性的软件,所以我们不使用太多的STL,因为动态内存分配通常在启动阶段之后被禁止。