1

两个简单的问题:简而言之,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 memory\n";
        abort();
    }
    return p;
}

int main()
{
    int *p = xnew<int>(5000000000LL);
}

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

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

4

4 回答 4

5

我不明白为什么这是必要的。 如果分配内存失败,new将抛出。std::bad_alloc如果您不处理异常,这将导致调用std::terminate有效地结束程序并具有与xmalloc.

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

于 2012-11-22T09:38:57.930 回答
3

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

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

你对函数调用的返回值所做的事情永远不会影响 C++ 中的模板参数推导。如果你写int *p = some_function(5000000000LL);thenint* 不一定是 的返回类型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.
于 2012-11-22T10:01:20.000 回答
1

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

  1. std::cerrbadbit在其exceptions()掩码中设置(默认情况下不是)
  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

异常开销实际上取决于实现。我相信现代编译器足够聪明,可以尽可能避免这种开销,但您应该使用您的平台进行检查以确定。

于 2012-11-22T10:06:49.617 回答
1

如果您想避免抛出异常new(无论出于何种原因 - 也许您正在使用不支持异常的平台,例如某些嵌入式平台),如果无法分配内存,您可以提供 anew_handler中止程序:new

#include <stdlib.h>

#include <iostream>
#include <new>

namespace {
    void new_handler_abort()
    {
        std::cerr << "Not enough memory\n";
        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因此可能仍会花费少量开销来处理永远不会发生的异常(较新的编译器可能会避免这种开销通过使用表驱动的堆栈展开,避免在每次调用时运行代码来设置异常)。
于 2012-11-22T10:34:55.940 回答