【问题标题】:Is it safe to #define NULL nullptr?#define NULL nullptr 是否安全?
【发布时间】:2012-02-18 16:17:55
【问题描述】:

我在许多最顶层的头文件中看到了下面的宏:

#define NULL 0  // C++03

在所有代码中,NULL0 可以互换使用。如果我把它改成。

#define NULL nullptr  // C++11

会不会造成不良的副作用?我能想到的唯一(好的)副作用是以下用法会变得不正确;

int i = NULL;

【问题讨论】:

    标签: c++ null c++11 nullptr


    【解决方案1】:

    我在最顶层的头文件中看到了下面的宏:

    你不应该看到,标准库在<cstddef>(和<stddef.h>)中定义了它。而且,IIRC,根据标准,重新定义标准头文件定义的名称会导致未定义的行为。所以从纯粹的标准角度来看,你不应该这样做。


    我见过人们做以下事情,无论出于何种原因,他们的心碎了:

    struct X{
      virtual void f() = NULL;
    }
    

    (如[错误]:“将虚拟表指针设置为NULL”)

    这仅在 NULL 定义为 0 时有效,因为 = 0 是纯虚函数 (§9.2 [class.mem]) 的有效标记。

    也就是说,如果NULL正确用作空指针常量,那么什么都不会中断。

    但是,请注意,即使看似正确使用,这也会改变:

    void f(int){}
    void f(char*){}
    
    f(0); // calls f(int)
    f(nullptr); // calls f(char*)
    

    但是,如果是这样的话,无论如何它几乎肯定是坏了。

    【讨论】:

    • virtual void f() = 0L; 合法吗?因为#define NULL 0L 肯定是符合规范的实现。
    • @iammilind:你不应该,因为NULL 已经被标准定义了。
    • @iammilind: 因为是UB重新定义标准宏。允许实现自己的头文件包含依赖于NULL 扩展为实现所说的任何扩展的代码。在 NULL 的情况下,如果实现(不明智地 IMO,但合法)在函数需要指针的上下文中将 NULL 作为可变参数传递,则可能会有所不同,因为知道 在其调用下是安全的约定NULL 大小合适,可以使用。如果您在包含该标头之前将其从 0L 更改为 0,那么您可能会破坏它。
    • @Steve: §9.2 [class.mem]: pure-specifier: = 0.
    • 顺便说一句,如果你正在编写 C++11 实现,那么 #define NULL nullptr 对你来说是合法的,因为 nullptr 是一个空指针常量,而 NULL 可以是任何空指针常量。但是程序不能自己决定应该是哪一个,这取决于实现。
    【解决方案2】:

    更好的是在整个代码中搜索 NULL 并将其替换为 nullptr

    它在语法上可能是安全的,但你会将#define 放在哪里?它会产生代码组织问题。

    【讨论】:

    • g++ -DNULL=nullptr ... :)
    • 我认为需要某些编译器标志的代码是不可移植的。
    • 好吧,我想我们不能再使用 -DWindows 或类似的东西了,我们需要重写所有代码以将其移植到另一个平台...
    • 有一个 optional -Dmode 开关是合理的,假设没有它代码仍然可以编译。此外,并非每个项目都需要跨平台,但在这种情况下,您不需要 -DWindows-DAnythingElse,因为它是内置的。
    【解决方案3】:

    没有。您不能(重新)定义标准宏。如果你看到

    #define NULL 0
    

    在标准头以外的任何文件的顶部(即使在那里,它 应该在包含警卫中,通常在附加警卫中作为 好),那么那个文件就坏了。删除它。

    请注意,好的编译器通常会用一些东西定义NULL 喜欢:

    #define NULL __builtin_null
    

    ,访问一个编译器,如果它是会触发一个警告 在非指针上下文中使用。

    【讨论】:

      【解决方案4】:

      您根本不应该定义它,除非您正在编写自己的<cstddef> 版本;它当然不应该出现在“许多最顶层的头文件”中。

      如果您要实现自己的标准库,那么唯一的要求是

      18.2/3 宏NULL是实现定义的C++空指针常量

      所以0nullptr 都是可以接受的,而nullptr 更好(如果你的编译器支持的话)因为你给出的原因。

      【讨论】:

        【解决方案5】:

        也许不会

        如果您有特定格式的重载行为:

        void foo(int);
        void foo(char*);
        

        然后是代码的行为:

        foo(NULL);
        

        将根据NULL是否更改为nullptr而改变。

        当然,还有另一个问题是编写此答案中存在的代码是否安全...

        【讨论】:

          【解决方案6】:

          虽然它可能会破坏与写得不好的旧东西的向后兼容性(要么那样,要么过于聪明......),但对于您的新代码,这不是问题。你应该使用nullptr,而不是NULL,你的意思是nullptr。另外,你应该使用0,你的意思是零。

          【讨论】:

          • 请记住,写得很好的旧东西也不会使用nullptr,因为它只是几个月前才添加到语言中。
          • @Mike:写得很好的旧东西可能有类似#define NULLPTR NULL的东西,但是当编译为C++ 11时可以更改为#define NULLPTR nullptr。然后,一旦您确信您将不再需要将其编译为 C++03,则代码中的所有 NULLPTR 实例都会被删除。
          • @SteveJessop 认真的吗?我的意思是,也许现在我们知道 nullptr NULLPTR 宏是有意义的,但是旧代码(之前 nullptr 被添加到标准中)似乎......不太可能。
          • 啊,我刚刚查了一下,有点记错了。 Stroustrup 的 C++ FAQ 说叫它nullptr,而不是NULLPTR。就我个人而言,我不会在 C++03 中为我知道将成为关键字的符号定义宏,这就是为什么我说 NULLPTR 而不是 nullptr。但它完全有效,前提是您确保在构建为 C++11 时未定义宏。
          • @SteveJessop "...我不会在 C++03 中为我知道将成为关键字的符号定义宏..." 这不正是包括这个特定的预处理器指令?如果您正在为 C++03 编写它,那么您根本不需要包含任何内容。我使用 #define nullptr NULL 在我的非 C++11 编译器上进行编译,因为我知道有一天我应该能够完全废弃宏。同时,我有更多可读的代码与最新的标准兼容。
          猜你喜欢
          • 2012-06-02
          • 2021-10-27
          • 2011-10-07
          • 1970-01-01
          • 2012-07-09
          • 2012-11-20
          • 2011-11-04
          • 2021-09-27
          • 2020-02-12
          相关资源
          最近更新 更多