C++中XMALoC的右模拟

C++中XMALoC的右模拟,c++,C++,两个简单的问题:在普通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(); }

两个简单的问题:在普通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);
}
第二个问题,如果我从xnew5000000000LL中删除;调用时,编译器g++4.7.2无法再推断[T=int],尽管返回类型int*仍然存在。为什么呢


编辑:使用新版本时,即使没有抛出异常,也会抛出异常,这难道没有任何开销吗?我真的不想在并非绝对必要的情况下使用任何异常。

我不明白为什么这是必要的。新将抛出std::bad\u alloc 如果它无法分配内存。如果不处理异常,则 将导致调用std::terminate,从而有效地结束 并具有与xmalloc相同的行为


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

我不明白为什么需要这样做。新将抛出std::bad\u alloc 如果它无法分配内存。如果不处理异常,则 将导致调用std::terminate,从而有效地结束 并具有与xmalloc相同的行为

当然,当编译器没有实现异常时,这种情况会发生变化

第二个问题,如果我从 x5000000000ll;调用,编译器g++4.7.2无法推断 [T=int]不再存在,尽管返回类型int*仍然存在。为什么? 是吗

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

您对函数调用返回值的处理不会影响C++中的模板参数推导。如果你写int*p=some\u函数5000000000ll;那么int*不一定是某个函数的返回类型,编译器将尝试将某个函数的返回类型转换为该类型

因此,编译器无法推断int的最接近的原因是,标准至少禁止它,而没有诊断。最终的原因是C++的设计者可能是Stroustrup,本来想限制考虑的事情。 作为推论,如果规则不简单,那么至少凡人的头脑可以理解

< C++中有一个规则,子表达式的类型只取决于子表达式本身,而不依赖于周围表达式。还有一个例外,当函数指针或成员函数指针不明确时:

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.
第二个问题,如果我从 x5000000000ll;调用,编译器g++4.7.2无法推断 [T=int]不再存在,尽管返回类型int*仍然存在。为什么? 是吗

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

您对函数调用返回值的处理不会影响C++中的模板参数推导。如果你写int*p=some\u函数5000000000ll;那么int*不一定是某个函数的返回类型,编译器将尝试将某个函数的返回类型转换为该类型

因此,编译器无法推断int的最接近的原因是,标准至少禁止它,而没有诊断。最终的原因是C++的设计者可能是Stroustrup,本来想限制考虑的事情。 作为推论,如果规则不简单,那么至少凡人的头脑可以理解

< C++中有一个规则,子表达式的类型只取决于子表达式本身,而不依赖于周围表达式。还有一个例外,当函数指针或成员函数指针不明确时:

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此代码不能保证100%安全,因为operator如果您希望避免因任何原因引发新的异常-可能您在不支持异常的平台上工作,如某些嵌入式平台,如果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;
}
仅将此源文件作为程序的一部分将安装一个新的_处理程序,该处理程序将中止程序的运行,这将导致分配内存时出现问题

然而:

除了调用main之前,这个init何时完成是不确定的。所以,如果在main之前遇到内存问题,它可能无法完全满足您的需要。 编译器可能仍然会添加代码以支持异常处理,对于某些编译器,其中包含每次调用operator new时发生的代码,因此可能仍然会有少量开销用于处理永远不会发生的异常。较新的编译器可能会通过使用表驱动堆栈展开来避免这种开销,从而避免 必须运行代码来设置每次调用的异常。
如果您希望避免因任何原因引发新的异常-可能您正在不支持异常的平台上工作,如某些嵌入式平台,您可以提供一个新的\u处理程序,以便在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;
}
仅将此源文件作为程序的一部分将安装一个新的_处理程序,该处理程序将中止程序的运行,这将导致分配内存时出现问题

然而:

除了调用main之前,这个init何时完成是不确定的。所以,如果在main之前遇到内存问题,它可能无法完全满足您的需要。 编译器仍然可以添加代码以支持异常处理,对于某些编译器,其中包含每次调用operator new时发生的代码,因此可能仍有少量开销用于处理永远不会发生的异常。较新的编译器可以通过使用表驱动堆栈展开来避免这种开销,从而避免必须运行代码来设置每次调用时的异常。

我们甚至不需要很小的开销,所以我们希望调用nothrow版本,但是xmalloc的开销与新版本相同,然后检查返回的指针。异常开销对我来说似乎并不重要,如果你的编程在以后会死掉的话。@MatteoItalia在g++中几乎没有开销有多大?:-@pmr:未捕获异常不一定具有与xmalloc相同的行为,因为它可能会解除堆栈,而abort则不会。我害怕去想代码有多糟糕才能让这件事发生,但是我有点害怕任何使用xmalloc的代码。还有一些奇怪的情况,你不能不考虑异常,例如从C库回调,在这种情况下,您必须捕获它并在某个地方中止。@马丁:在g++中,使用抛出新代码的开销是,如果您使用NOTHOW new,编译器可能会错过一些优化,但如果您使用NOTHOW new,则可能会证明某个代码块不会抛出。我们不希望有任何微小的开销,所以我们想调用nothrow版本,但是xmalloc的开销与新版本相同,然后检查返回的指针。异常开销对我来说似乎并不重要,如果你的编程在以后会死掉的话。@MatteoItalia在g++中几乎没有开销有多大?:-@pmr:未捕获异常不一定具有与xmalloc相同的行为,因为它可能会解除堆栈,而abort则不会。我害怕去想代码有多糟糕才能让这件事发生,但是我有点害怕任何使用xmalloc的代码。还有一些奇怪的情况,你不能不考虑异常,例如从C库回调,马丁:在g++中,使用抛出new的开销是,如果你没有使用new,编译器可能会错过一些优化,如果你没有使用new,编译器可能会证明某个代码块没有抛出。new会抛出bad\u alloc是它无法分配它的吗如果您未捕获,turn将终止您的程序。这正是你想要的。为什么要这么做?如果你没有告诉编译器你想用什么类型实例化模板,编译器怎么知道返回类型是int*呢?我能理解你为什么要这么做。您的实现似乎很好。您的第二个问题似乎有点奇怪,因为显然,您需要为编译器提供模板类型以推断任何类型。此外,编译器不应该推断类型,无论返回参数是int。可能是您在代码中出错,因此编译器建议这是一个错误。我宁愿编译器这样做,也不愿高兴地随意推断。请注意,newnothrow的默认实现应该调用普通运算符new,捕获异常并返回空指针。new将抛出bad_alloc,因为它无法分配,如果你没有捕获它,它将终止你的程序。这正是你想要的。为什么要这么做?如果你没有告诉编译器你想用什么类型实例化模板,编译器怎么知道返回类型是int*呢?我能理解你为什么要这么做。您的实现似乎很好。您的第二个问题似乎有点奇怪,因为显然,您需要为编译器提供模板类型以推断任何类型。此外,编译器不应该推断类型,无论返回参数是int。可能是您在代码中出错,因此编译器建议这是一个错误。我宁愿编译器这样做,也不愿高兴地随意推断。请注意,newnothrow的默认实现应该调用普通运算符new,捕获异常并返回空指针。我的两个问题都得到了回答。非常感谢你对我的第二个问题的简洁解释
我的两个问题都得到了回答。非常感谢你对我的第二个问题的简洁解释。