标准头文件<new>中的std::nothrow和std::new_handler有什么用途?

3

我发现了一个小型的标准头文件<new>。可能之前我没有直接使用过它。以下是我感兴趣的部分:这里是g++版本,供有兴趣的人参考。

  struct nothrow_t { };
  extern const nothrow_t nothrow;
  /** If you write your own error handler to be called by @c new, it must
   *  be of this type.  */
  typedef void (*new_handler)();
  /// Takes a replacement handler as the argument, returns the previous handler.
  new_handler set_new_handler(new_handler) throw();
  1. 程序员如何使用struct nothrow_t及其对象nothrow?对象是否需要被声明为extern
  2. new_handler在什么情况下会被使用?
  3. 为什么所有的operator new/delete都要在extern C++块中声明?

1
不是重复的问题,但是这个问题的答案可以回答你的一些问题:我应该如何编写符合ISO C++标准的自定义new和delete运算符? - Alok Save
2个回答

5

nothrow_t 用于告诉 operator new 以向后兼容的“在失败时返回 Null 而不是抛出异常”的模式进行操作。

也就是说,如果您看到以下代码:

int * idx = new(std::nothrow) int;

这里用到的是nothrow_t。有关标准中相关部分,请从(截至C++11 N3376)17.6.4.6 [replacement.functions]/1开始,然后向下查找。

回答您的具体问题:

  1. 根据18.6 [support.dynamic]/1的规定,这确实必须是extern的,其中包括:

    namespace std {
        class bad_alloc;
        class bad_array_new_length;
        struct nothrow_t {};
        extern const nothrow_t nothrow;
        typedef void (*new_handler)();
        new_handler get_new_handler() noexcept;
        new_handler set_new_handler(new_handler new_p) noexcept;
    }
    

    此外,17.6.2.3 [using.linkage]/1说“C++标准库中的实体具有外部链接性(3.5)”。函数和类(例如上面的get_new_handlerset_new_handler)不需要显式注释为具有外部链接性,因为它们默认具有外部链接性。

  2. 当用户通过调用set_new_handler覆盖正在使用的默认operator new时,会使用new_handler。它只是一个函数指针类型。

  3. 可能是因为operator new的签名在C中没有被保留。extern "C++"告诉编译器可以对这些函数进行名称重整和其他特定于C ++的操作。这样,您可以将一个翻译单元编译为C,将另一个翻译单元编译为C ++,并将它们链接到相同的二进制文件中,而不必担心C语言中的某个人定义了与编译器的operator new冲突的函数。

关于第二点,请提供参考/示例。 cplusplus 的示例很令人困惑。它是否只适用于重载的 new?我认为它也适用于现有的 operator new - iammilind
@iammilind:我从未使用过这个功能,所以现在提供示例让我感到有些不舒服,在一般情况下,我认为这通常是一个坏主意。虽然它在语言中有用例,但我会感到惊讶,如果超过0.001%的程序这样做。(即使在人们想要使用自定义分配器的情况下,他们通常会为特定类型覆盖new;或者只是制作一个std::allocator实现,而不是覆盖全局operator new - Billy ONeal
@iammilind:我不明白你所说的“重载new”和“现有的operator new”之间的区别。 set_new_handler仅覆盖全局operator new实现。如果存在类型特定的operator new重载,则将使用该重载,即使某人在其他地方调用了set_new_handler - Billy ONeal

2
好的,这是一个“请阅读文档”的问题。任何一本关于C++的好入门书都应该讨论nothrow。例如,Bjarne的《C++程序设计语言》中有涉及。
但无论如何,你可以使用nothrowstd::bad_alloc异常转换为空指针结果,并使用new handler来可能重试失败的分配。
实际上,当你使用nothrow功能时,记得在new前面加上::,并且通常在使用全局放置new时也要这样做,以避免从类中获取放置new。除了通常应避免使用放置new(作为非常低级别的语言特性),我会将这个习惯用于即使在技术上没有意义时也要这样做

示例:

#include <iostream>     // std::wcout, std::endl
#include <stdlib.h>     // EXIT_FAILURE, EXIT_SUCCESS
#include <new>          // std::nothrow
using namespace std;

int main()
{
    int* const p = ::new( std::nothrow ) int[0x7fffffff/sizeof(int)];
    if( !p )
    {
        cout << "Allocation failed!" << endl;
        return EXIT_FAILURE;
    }
    cout << "Alles success!" << endl;
    delete[] p;
    return EXIT_SUCCESS;
}

我的系统输出:

[D:\dev\test]
> a
分配失败!
[D:\dev\test] > _

请注意,以上假设为32位进程 :)


+1 -- 注意以上假设是在32位机器和32位进程下 :) (并且int至少有32位的存储空间) - Billy ONeal
@Billy:谢谢,我本来想加那个注释的,但是我妈妈刚起床,总是要求关注。无论如何,虽然你的用心良苦,请不要更正我的代码。你添加的更正(<stdlib.h>-> <cstdlib>)是不好的。使用<cstdlib>头文件并没有任何优势,反而有一些缺点。将好的代码改为使用该头文件尤其不好。 - Cheers and hth. - Alf
@Billy:另一方面,“int至少有32位存储空间”是不正确的。它并没有。int至少有16位存储空间,仅此而已。 - Cheers and hth. - Alf
在使用new(nothrow)时,是否将::放在其前面是一个好的习惯还是必须这样做? - iammilind
@Cheersandhth.-Alf:1.我改了它,因为这是一个C++问题。我认为这样做的好处在于与C++标准库中的其他头文件保持一致。但好吧,我不会再改回去了。2.不,你的常量0x7fffffffint类型,需要32位存储空间。如果int只有16位,则行为将是未定义的(有符号整数溢出是未定义的)。 - Billy ONeal
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接