简体   繁体   English

C ++中xmalloc的正确模拟

[英]Right analogue of xmalloc in C++

Two simple questions: In plain C we frequently use xmalloc which is a allocate-or-abort routine. 两个简单的问题:在普通C我们经常使用xmalloc ,它是一个分配或中止例程。 I implemented it in C++. 我用C ++实现。 Is this a right exception-free implementation? 这是正确的无异常实现吗?

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);
}

Second question, if I remove the <int> from the xnew<int>(5000000000LL); 第二个问题,如果删除<int>xnew<int>(5000000000LL); call, compiler (g++ 4.7.2) cannot infere that [T = int] anymore although the return type int * is still there. 调用时,尽管返回类型int *仍然存在,但是编译器(g ++ 4.7.2) 不能再推断 [T = int] Why is that? 这是为什么?

Edit: Isn't there any overhead when using the new version which could throw exception even if it's not thrown? 编辑:使用new版本时,即使没有抛出异常也不会产生任何开销吗? I really don't want to use any exceptions when not absolutely necessary. 当不是绝对必要时,我真的不想使用任何异常。

I fail to see why this is necessary. 我看不出为什么这是必要的。 new will throw std::bad_alloc if it fails to allocate memory. 如果无法分配内存,则new将抛出std::bad_alloc If you don't handle exceptions, this will lead to a call to std::terminate which effectively ends the program and has the same behavior as xmalloc . 如果不处理异常,则将导致对std::terminate的调用,该调用将有效地结束程序并具有与xmalloc相同的行为。

Of course, this changes when your compiler does not implement exceptions. 当然,当您的编译器未实现异常时,这种情况会改变。

Second question, if I remove the <int> from the xnew<int>(5000000000LL); 第二个问题,如果删除<int>xnew<int>(5000000000LL); call, compiler (g++ 4.7.2) cannot infere that [T = int] anymore although the return type int * is still there. 调用时,尽管返回类型int *仍然存在,但是编译器(g ++ 4.7.2)不能再推断[T = int]。 Why is that? 这是为什么?

Function template arguments are deduced only from the types of the argument expressions in the function call. 仅从函数调用中参数表达式的类型推导函数模板参数。 Since T doesn't appear in the function parameters in any way, it cannot be deduced. 由于T不会以任何方式出现在函数参数中,因此无法推导它。

What you do with the return value of the function call doesn't affect template argument deduction in C++, ever. 使用函数调用的返回值所做的操作永远不会影响C ++中的模板参数推导。 If you write int *p = some_function(5000000000LL); 如果您写int *p = some_function(5000000000LL); then int* is not necessarily the return type of some_function , it's a type to which the compiler will attempt to convert the return type of some_function . 然后int* 不一定的返回类型some_function ,这是一个类型,编译器将尝试的返回类型转换some_function

So the proximal reason that the compiler can't deduce int is that the standard forbids it (at least, without a diagnostic). 因此,编译器无法推断int的最接近原因是该标准禁止使用int (至少在没有诊断的情况下)。 The ultimate reason is that the designers of C++ (probably Stroustrup originally) wanted to limit the things taken into consideration for deduction, to keep the rules if not simple then at least comprehensible to mortal minds. 最终的原因是C ++的设计人员(最初可能是Stroustrup)想要限制演绎时要考虑的事情,以保持规则(如果不是简单的话)至少可以被凡人所理解。

There's a rule in C++ that the type of a sub-expression only depends on the sub-expression itself, not on the surrounding expression. C ++中有一条规则,即子表达式的类型仅取决于子表达式本身,而不取决于周围的表达式。 And AFAIK there's only one exception, which is when a function pointer or member-function pointer is ambiguous: 而且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.

This code is not guaranteed to be 100% safe because operator<<() could throw . 不能保证此代码是100%安全的,因为operator<<() 可能会抛出 Practically it's not widespread case because to throw there shall be some rare conditions met: 实际上,这不是一个普遍的情况,因为抛出将满足一些罕见条件:

  1. std::cerr has badbit set in its exceptions() mask (by default it's not) std::cerrexceptions()掩码中设置了badbit (默认情况下不是)
  2. Exception is thrown during output 输出期间引发异常

In this case exception will be rethrown and memory will leak. 在这种情况下,异常将被重新抛出并且内存将泄漏。

About removing <int> from template function call expression - of course it will not work. 关于从模板函数调用表达式中删除<int> -当然,它将不起作用。 Compiler could deduce template arguments type only from call expression itself, not from lvalue type that it will be assigned. 编译器只能从调用表达式本身推断模板参数类型,而不能从将要分配的左值类型推断模板参数类型。 So template arguments that you want to be auto-deduced shall be function arguments, not return type: 因此,您要自动推导的模板参数应该是函数参数,而不是返回类型:

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

Exceptions overhead actually depends on implementation. 异常开销实际上取决于实现。 I believe modern compilers are smart enough to avoid such overhead if possible, but you shall check it with your platform to be sure. 我相信现代编译器足够聪明,可以避免此类开销,但是请确保与平台一起检查。

If you want to avoid an exception throwing new (for whatever reason - maybe you're working on a platform where exceptions aren't supported, like some embedded platforms), you can provide a new_handler to abort the program if new can't allocate memory: 如果您想避免引发new异常(无论出于何种原因-也许您正在不支持异常的平台(例如某些嵌入式平台)上工作),则可以提供new_handlernew无法分配的new_handler下中止程序记忆:

#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;
}

Merely including this source file as part of your program will install a new_handler that will abort the program is new has trouble allocating memory. 仅将此源文件包含在您的程序中会安装一个new_handler ,它将在程序是new时中止该程序,从而无法分配内存。

However: 然而:

  • it's not deterministic when this init will be done (other than it'll happen before main() is called). 何时完成此初始化并不确定(除了在调用main()之前会发生)。 So if you run into memory problems before main() , it might not do exactly what you want. 因此,如果在main()之前遇到内存问题,它可能无法完全满足您的要求。
  • the compiler may still add code to support exception handling, and for some compilers that includes code that occurs on each call to operator new so there may still be some small amount of overhead spent to deal with exceptions that will never occur (newer compilers may avoid this overhead by using a table-driven stack unwind that avoids having to run code to set up exceptions on each call). 编译器还可以添加代码来支持异常处理,而对于一些编译器,其包括在每次调用发生代码operator new所以有可能仍然是架空的一些少量的花费来处理决不会发生异常(较新的编译器可避免通过使用表驱动的堆栈展开来避免这种开销,从而避免了必须运行代码以在每次调用时设置异常的情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM