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

时间:2016-10-10 07:19:12

标签: c++

我们有一个算法库,对可能是NaN的数字执行大量std::min / std::max次操作。考虑到这篇文章:Why does Release/Debug have a different result for std::min?,我们意识到它显然不安全。

有没有办法阻止开发人员使用std::min / std::max

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

顺便说一下,使用此函数的遗留代码(如STL标头)不应受到影响。只有我们编写的代码才会受到影响。

9 个答案:

答案 0 :(得分:31)

我不认为使标准库函数不可用是正确的方法。首先,NaN是浮点值如何工作的一个基本方面。您需要禁用所有其他类似的内容,例如sort()lower_bound()等。此外,程序员因创造性而获得报酬,我怀疑任何程序员都是{{1}如果std::max()无效,我会犹豫使用a < b? b: a

另外,对于没有NaN的类型,例如整数或字符串,您显然不想禁用std::max(a, b)std::max()。所以,你需要一种有些控制的方法。

没有可移植的方法来禁用命名空间std::min()中的任何标准库算法。您可以通过提供适当的std d重载来定位这些算法的使用来破解它,例如:

delete

答案 1 :(得分:17)

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

我相信强迫他们只会让他们想出来解决问题。

答案 2 :(得分:7)

由于不允许修改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&));
}

Demo

之前

#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

答案 3 :(得分:4)

没有便携式这样做的方法,因为除了几个例外,您不能更改std中的任何内容。

然而,一个解决方案是

#define max foo

在包含任何代码之前

。然后std::maxmax都会发出编译时失败。

但实际上,如果我是你,我会习惯你平台上std::maxstd::min的行为。如果他们没有做标准所说的应该做的事情,那么就向编译器供应商提交一份错误报告。

答案 4 :(得分:4)

如果在调试和发布中得到不同的结果,则问题不会得到不同的结果。问题是一个版本,或者可能两个版本都是错误的。并且不能通过禁止std :: min或std :: max或用具有已定义结果的不同函数替换它们来解决这个问题。您必须确定每个函数调用实际需要哪个结果才能获得正确的结果。

答案 5 :(得分:3)

我不打算完全回答你的问题,但不是完全禁止std::minstd::max,你可以教育你的同事,并确保你一直使用总订单比较而不是每当您使用依赖于给定订单的函数时,原始operator<(由许多标准库算法隐式使用)。

这种比较器被提议用于P0100 — Comparison in C++(以及部分和弱阶比较器)的标准化,可能针对C ++ 20。同时,C标准委员会已经在TS 18661 — Floating-point extensions for C, part 1: Binary floating-point arithmic上工作了一段时间,显然是针对未来的C2x(应该是~C23),它更新了<math.h>标题以及实现最近所需的许多新功能ISO/IEC/IEEE 60559:2011 standard。在新功能中,有totalorder(第14.8节),根据IEEE totalOrder比较浮点数:

  

totalOrder( x y )对规范成员的格式为 x y

     
      
  1. 如果 x &lt; y ,totalOrder( x y )为真。
  2.   
  3. 如果 x &gt; y ,totalOrder( x y )为false。
  4.   
  5. 如果 x = y      
        
    1. totalOrder(-0,+ 0)为真。
    2.   
    3. totalOrder(+ 0,-0)为false。
    4.   
    5. 如果 x y 表示相同的浮点数据:      
          
      • 如果 x y 有负号,则totalOrder( x y )为真如果 x 的指数≥ y 的指数。
      •   
      • 当且仅当 x 的指数≤ y <的指数时,totalOrder( x y )才为真/ em>的
      •   
    6.   
  6.   
  7. 如果 x y 在数字上无序,因为 x y 是NaN:      
        
    1. totalOrder(-NaN, y )为真,其中-NaN表示带负号位的NaN, y 是浮点数。
    2.   
    3. totalOrder( x ,+ NaN)为真,其中+ NaN表示具有正符号位的NaN, x 是浮点数。
    4.   
    5. 如果 x y 都是NaN,则totalOrder会根据以下内容反映总排序:      
          
      • 低于正号的负号订单
      •   
      • 信号顺序低于+ NaN低于安静状态,反向为-NaN
      •   
      • 较小的有效载荷,当被视为一个整数时,为+ NaN订购低于更大的有效载荷,对于-NaN则为反向。
      •   
    6.   
  8.   

这是一面文字,所以这里有一个列表,有助于看到什么比什么更大(从大到小):

  • 正面安静的NaNs(按有效载荷排序,视为整数)
  • 正信号NaNs(按有效载荷排序,视为整数)
  • 正无穷大
  • 正面实力
  • 正零
  • 负零
  • 负面实力
  • 负无穷大
  • 否定信令NaN(按有效载荷排序,视为整数)
  • 负安静NaNs(按有效负载排序,视为整数)

不幸的是,这个总订单目前缺少库支持,但是可能会将自定义总订单比较器一起破解浮点数并在您知道将要比较的浮点数时使用它。一旦你掌握了这样一个总订单比较器,你就可以安全地在需要的地方使用它,而不是简单地禁止std::minstd::max

答案 6 :(得分:2)

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

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

使用它们会发出编译错误:

error: attempt to use poisoned "min"

C ++中唯一的问题是你只能毒害“标识符令牌”,而不是std::minstd::max,所以实际上还会毒害所有函数和局部变量名称minmax ...可能不是您想要的,但如果您选择Good Descriptive Variable Names™,则可能不是问题。

  

如果中毒标识符作为扩展宏的一部分出现   这是在标识符中毒之前定义不会   导致错误。这可以让你毒害标识符而不用担心   关于定义使用它的宏的系统头文件。

     

例如,

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

不会产生错误。

阅读链接以获取更多信息,当然。

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

答案 7 :(得分:1)

您已弃用std :: min std :: max。您可以通过使用grep进行搜索来查找实例。或者你可以摆弄标题本身来打破std :: min,std :: max。或者您可以尝试将min / max或std :: min,std :: max定义到预处理器。由于C ++命名空间,后者有点狡猾,如果你定义std :: max / min你不能使用命名空间std,如果你定义了min / max,你也可以选择使用这些标识符。 / p>

或者,如果项目有标准标题,例如&#34; mylibrary.lib&#34;每个人都包括,打破std :: min / max。

当然,当传递NaN时,函数应该返回NaN。但是编写它们的自然方式总会引发错误。

答案 8 :(得分:0)

IMO失败的C ++语言标准要求min(NaN,x)和min(x,NaN)返回NaN,并且对于max同样,这是C ++语言标准中的一个严重缺陷,因为它掩盖了以下事实: NaN已生成,并导致令人惊讶的行为。很少有软件开发人员进行足够的静态分析以确保永远不会为所有可能的输入值生成NaN。因此,我们为min和max声明了自己的模板,对float和double进行了专门化处理,以使用NaN参数给出正确的行为。这对我们有用,但对使用STL更大部分的人可能不起作用。我们的领域是高完整性软件,因此我们不会使用太多的STL,因为通常在启动阶段之后就禁止动态内存分配。