【问题标题】:Operator new overloading c++, handling failure without exception运算符new重载c++,处理失败无异常
【发布时间】:2015-09-24 11:49:55
【问题描述】:

我正在开发一个不使用异常的项目,并且应该保持这种状态。 为了使用我自己的内存管理器,我重载了(重写可能更正确)新运算符。在我的内存管理器中,如果分配失败 - 将返回 NULL。 现在虽然 new 运算符返回 NULL,但正在调用构造函数,然后我得到一个 seg 错误,因为我没有分配内存。 我想要的功能是,如果 new 运算符返回 NULL,则不应调用构造函数(之后我将检查对象是否已成功初始化)。

我希望它像这样工作:

myObject = new object(...);
if (NULL == myObject)
    printf("error")
else
    do something

谢谢!

【问题讨论】:

  • operator new and operator new[] functions 只分配内存,当您使用new 运算符时,编译器会创建代码来调用可能的构造函数。返回空指针(nullptr0)是失败时的正确行为。
  • new object(...) 分配内存并调用构造函数。我希望它仅在分配成功时才调用构造函数。我知道我需要的 nothrow 选项,但是在重载时如何做这样的事情?
  • @JoachimPileborg 仅当您通过 noexceptthrow() 将分配函数声明为非抛出时,请参阅 [basic.stc.dynamic.allocation]p3 - 我不清楚是否这是在禁用异常时暗示的。

标签: c++ operator-overloading new-operator


【解决方案1】:

你需要调用new (nothrow),像这样:

myObject* = new (std::nothrow) myObject(...);

然后,您需要覆盖带有std::nothrow_toperator new,而不是覆盖常规operator new。有关更多信息,请参见此处:http://www.cplusplus.com/reference/new/nothrow/

没有nothrow 的常规operator new 不应该在失败时返回null,而是应该抛出。由于不允许投掷,因此无法实现这些运算符(除非在失败时调用 abort() 或类似的东西)。

最后,在编译期间完全禁用异常可能会有所帮助。我希望那时会发现这种错误(我不确定)。

【讨论】:

  • 这不是唯一的方法,特别是如果您正在编写自己的operator new。 [basic.stc.dynamic.allocation]p3 "如果具有非抛出异常规范的分配函数分配存储失败,则应返回空指针。任何其他分配存储失败的分配函数应指示失败只能通过抛出与 std::bad_alloc 类型的处理程序匹配的类型的异常。"
  • @dyp:“具有非抛出异常规范的分配函数”与采用std::nothrow_toperator new 有何不同?在我看来,nothrownoexcept,而“常规”则不是,所以我不完全确定你在开什么车。
  • std::nothrow_t 不是异常规范。另外,我在引用中删除了一个引用:在 "non-throwing exception speficiation" 之后,[basic.stc.dynamic.allocation]p3 指的是 [except.spec],这是关于“异常规范”。虽然您不能用noexcept 替换默认的operator new(size_t),但您可以为noexcept 的类类型编写自定义分配函数。
  • 在 C++03 中,相同的段落是:"如果使用空异常规范 (15.4) throw() 声明的分配函数未能分配存储,则应返回一个空指针。”
  • @user3445972 请提供Minimal, Complete, Verifiable Example。下面是一个如何替换分配和释放函数的示例,以便它们可以通过返回空指针而失败:coliru.stacked-crooked.com/a/dcb4aa772cdfd89e
【解决方案2】:

最终解决方案是在 new 的函数声明末尾添加“throw()”。

void* operator new (size_t size) throw()

可以在常规 new 或 new (nothrow) 中完成。在我的实施中,将它添加到常规 new 中会产生预期的效果,即任何“新”调用都能够在失败时返回 NULL。

还有一个选项可以在编译中使用“--force_new_nothrow”标志。

可以找到关于两者的更多信息 here

【讨论】:

    【解决方案3】:

    您必须手动完成,即。

    object * create_object(MemoryManager & mem, ...) {
       void * chunk = mem.get_memory_for_object();
       if (chunk == NULL) return NULL;
       return new (chunk) object(...); // inplace new 
    }
    

    【讨论】:

      【解决方案4】:

      我假设您想提供一个自定义的、特定于类的分配函数,而不是在全局命名空间中重载 new 运算符。

      tl;博士

      自 C++11 起:使用 noexcept 声明您的自定义 operator new,一切正常。

      void* T::operator new (size_t size) noexcept;
      

      完整答案

      虽然 John 给出的当前接受的答案提出了一个可行的解决方案,但它暗示std::nothrow 标签是必要的,没有它就无法做到,这是错误的。

      此答案只是@dyp 的(正确)评论,扩展为完整答案并带有标准的当前引用。

      C++11 standard 在 [basic.stc.dynamic.allocation] 第 3 段中说:

      如果使用非抛出异常规范 (15.4) 声明的分配函数未能分配存储,它应返回一个空指针。任何其他分配存储失败的分配函数只能通过抛出与 std::bad_alloc (18.6.2.1) 类型的处理程序 (15.3) 匹配的类型的异常来指示失败。

      多年来,这句话已被重写,但在 C++14、C++17 和 C++20 中出现了语义等价的内容。 The current working draft for C++23 在 [basic.stc.dynamic.allocation] 中有这一段:

      具有非抛出异常规范 (14.5) 的分配函数通过返回空指针值来指示失败。任何其他分配函数从不返回空指针值,并且仅通过抛出与 std::bad_alloc (17.6.4.1) 类型的处理程序 (14.4) 匹配的类型的异常 (14.2) 来指示失败。

      “非抛出异常规范”在 [except.spec] 中定义。如果您将noexcept 作为后缀添加到声明中,则函数具有此规范。因此,问题中给出的代码可以完全正常工作,但是必须使用 noexcept 声明自定义 new 运算符:

      void* T::operator new (size_t size) noexcept;
      

      或者,直到 C++17,使用 throw() 而不是 noexcept 也可以工作,但它已在 C++11 中被弃用并在 C++20 中被删除。这是 C++11 之前要走的路。 OP最终根据他们自己的答案使用了这个。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-12-18
        • 2021-12-17
        • 2014-02-21
        • 1970-01-01
        • 2021-07-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多