C++中xmalloc的右类似物

Right analogue of xmalloc in C++

本文关键字:类似物 xmalloc C++      更新时间:2023-10-16

两个简单的问题:简单来说C我们经常使用xmalloc,这是一个分配或中止例程。我在C++年实现了它。这是一个正确的无异常实现吗?

template <typename T>
T *xnew(const size_t n)
{
T *p = new (std::nothrow) T[n];
if (p == nullptr)
{
cerr << "Not enough memoryn";
abort();
}
return p;
}
int main()
{
int *p = xnew<int>(5000000000LL);
}

第二个问题,如果我从xnew<int>(5000000000LL);调用中删除<int>,编译器(g++ 4.7.2)无法再推断[T = int],尽管返回类型int *仍然存在。为什么?

编辑:使用new版本时是否有任何开销,即使没有抛出也可能引发异常?我真的不想在非绝对必要时使用任何例外。

我不明白为什么这是必要的。new会扔std::bad_alloc如果它无法分配内存。如果不处理异常,则此 将导致对std::terminate的调用,从而有效地结束 程序,并且具有与xmalloc相同的行为。

当然,当您的编译器不实现异常时,这种情况会发生变化。

第二个问题,<int>如果我从xnew<int>(5000000000LL);调用中,编译器 (g++ 4.7.2) 无法推断 [T = int] 了,尽管返回类型 int * 仍然存在。为什么 那是?

函数模板参数仅从函数调用中的参数表达式类型推导。由于T不会以任何方式出现在函数参数中,因此无法推断。

您对函数调用的返回值执行的操作不会影响C++中的模板参数推导。如果你写int *p = some_function(5000000000LL);那么int*不一定some_function的返回类型,它是编译器将尝试将返回类型转换为some_function的类型。

因此,编译器无法推断出int的近端原因是标准禁止它(至少,没有诊断)。最终的原因是C++的设计者(可能是Stroustrup最初)想要限制考虑的事情。 对于演绎,保持规则,如果不是简单的,那么至少是凡人可以理解的。

C++有一条规则,即子表达式的类型仅取决于子表达式本身,而不取决于周围的表达式。AFAIK 只有一个例外,即函数指针或成员函数指针不明确时:

void foo();
void foo(int);
void (*pfoo1)() = &foo; // &foo evaluates to a pointer to the void overload
void (*pfoo2)(int) = &foo; // &foo evaluates to a pointer to the int overload
void (*pfoo3)() = (void(*)(int))&foo; // &foo evaluates to the int overload, but doesn't convert to the type of pfoo3 so the line fails.

此代码不能保证 100% 安全,因为operator<<()可能会抛出。实际上,这种情况并不普遍,因为投掷应该满足一些罕见的条件:

  1. std::cerr在其exceptions()掩码中设置了badbit(默认情况下不是)
  2. 输出期间引发异常

在这种情况下,将重新引发异常,内存将泄漏。

关于从模板函数调用表达式中删除<int>- 当然它不起作用。编译器只能从调用表达式本身推断模板参数类型,而不是从将要分配的左值类型推断模板参数类型。所以你想要自动推导的模板参数应该是函数参数,而不是返回类型:

template <class T> T f1();
template <class T> T f2(T);
int a = f1();   // Will not compile, shall be f1<int>();
int b = f2(42); // OK

异常开销实际上取决于实现。我相信现代编译器足够聪明,如果可能的话,可以避免这种开销,但你应该用你的平台检查一下,以确保。

如果你想避免异常抛出new(无论出于何种原因 - 也许你正在一个不支持异常的平台,比如一些嵌入式平台),你可以提供一个new_handler,以便在new无法分配内存时中止程序:

#include <stdlib.h>
#include <iostream>
#include <new>
namespace {
void new_handler_abort()
{
std::cerr << "Not enough memoryn";
abort();
}
struct new_handler_abort_installer {
new_handler_abort_installer() {
std::set_new_handler(new_handler_abort);
}
};

// a statically allocated object that does nothing but install the
//  new_handler_abort() function as the new_handler
new_handler_abort_installer install_new_handler_abort;
}

仅将此源文件作为程序的一部分包含将安装一个new_handler,该程序将中止程序new分配内存时遇到问题。

然而:

  • 何时完成此初始化不是确定性的(除了它会在调用main()之前发生)。 因此,如果您在main()之前遇到内存问题,它可能无法完全按照您的要求执行。
  • 编译器
  • 可能仍会添加代码以支持异常处理,并且对于某些编译器,它包含每次调用operator new时发生的代码,因此可能仍会花费少量开销来处理永远不会发生的异常(较新的编译器可以通过使用表驱动的堆栈展开来避免此开销,从而避免在每次调用时运行代码来设置异常)。