[当构造函数抛出异常并使用自定义的new时,C ++如何释放内存

问题描述 投票:13回答:5

我看到以下结构:

  • [new X将释放内存,如果抛出X构造函数。

  • operator new()可以重载。

[运算符新的重载的规范定义是void *operator new(size_t c, heap h)和相应的operator delete

最常见的运算符new重载是new位置,即void *operator new(void *p) { return p; }

您几乎总是不能在放置在位置delete的指针上调用new

这导致一个单一的问题:X构造函数抛出并使用重载的new时如何清理内存?

c++ exception new-operator language-lawyer
5个回答
1
投票

[当构造函数引发异常时,将调用匹配的delete。不会为抛出的类调用析构函数,但是成功调用了其构造函数的类的任何组件都将调用其析构函数。


5
投票

根本上,如果没有与delete运算符相对应的new运算符,则不执行任何操作。在新放置的情况下也不会执行任何操作,因为相应的放置删除操作符是no-op。异常不会被转移:它会继续前进,因此新调用者有机会(和负责)释放分配的内存。

称为newplacement,因为它用于将对象放置在以其他方式获取的内存中;由于内存不是由新操作员获取的,因此删除操作不太可能释放该内存。实际上,这个问题是有争议的,因为(至少从C ++ 03起)不允许替换placement new运算符(其原型为operator new(size_t, void*)或删除(operator delete(void*, void*))。提供的placement new运算符返回其第二个参数,并且提供的展示位置删除运算符为无操作。

其他newdelete运算符可以全局替换或特定类替换。如果调用了自定义new运算符,并且构造函数抛出异常,并且存在相应的delete运算符,则将在删除该异常之前传播该delete运算符以进行清理。但是,如果没有相应的delete运算符,这不是错误。


2
投票

首先,一个例子:

#include <cstddef>
#include <iostream>

struct S
{
    S(int i) { if(i > 42) throw "up"; }

    static void* operator new(std::size_t s, int i, double d, char c)
    {
        std::cout << "allocated with arguments: "
                  <<i<<", "<<d<<", "<<c<<std::endl;
        return new char[s];
    }

    static void operator delete(void* p, int i, double d, char c)
    {
        std::cout << "deallocated with arguments: "
                  <<i<<", "<<d<<", "<<c<<std::endl;
        delete[] (char*)p;
    }

    static void operator delete(void* p)
    {
        std::cout << "deallocated w/o arguments"<<std::endl;
        delete[] (char*)p;
    }
};

int main()
{
    auto p0 = new(1, 2.0, '3') S(42);

    S* p1 = nullptr;
    try
    {
        p1 = new(4, 5.0, '6') S(43);
    }catch(const char* msg)
    {
        std::cout << "exception: "<<msg<<std::endl;
    }

    delete p1;
    delete p0;
}

输出:

分配了参数:1、2、3分配有参数:4、5、6取消分配参数:4、5、6例外:向上没有参数的释放

运算符新的重载的规范定义是void *operator new(std::size_t, heap h)

我不知道这是如何规范的,因为不允许这样做:好的,现在是new:)

的有效放置形式

[basic.stc.dynamic.allocation] / 1

分配函数应为类成员函数或全局函数;如果在全局范围以外的名称空间范围中声明了分配函数,或者在全局范围中声明了静态函数,则程序的格式不正确。返回类型应为void*第一个参数的类型应为std::size_t。第一个参数不得具有关联的默认参数。 第一个参数的值应解释为分配的请求大小。

[我的重点]

您可以重载要为new的放置形式调用的分配函数,请参见[expr.new](对于非模板函数,[basic.stc.dynamic.allocation]中未明确允许。不被禁止)。 new(placement)中给出的位置在此一般化为expression-list。特定new-expressionexpression-list中的每个expression作为附加参数传递给分配函数。如果调用了释放函数(例如,由于被调用的ctor引发异常),则将相同的参数加上前导void*(分配函数的返回值)传递给释放函数。

[expr.new] / 18个状态:

如果上述对象初始化的任何部分因引发异常而终止,已经为对象获取了存储,并且可以找到合适的释放函数,则调用释放函数以释放正在构造对象的内存,此后异常将继续在new-expression的上下文中传播。如果找不到明确的匹配释放函数,则传播异常不会导致释放对象的内存。 [注意:当被调用的分配函数未分配内存时,此方法适用;否则,很可能导致内存泄漏。 -尾注]

和/ 21

如果new-expression调用释放函数,则它将分配函数调用返回的值作为void*类型的第一个参数传递。如果调用了布局释放函数,则将传递与传递给布局分配函数的参数相同的附加参数,即与使用new-placement语法指定的参数相同的参数。

和/ 20

如果具有相同数量的参数,并且在参数转换后,除第一个参数外的所有参数类型都相同,则布局释放函数的声明与布局分配函数的声明匹配。任何非布局释放函数都与非布局分配函数匹配。如果查找找到单个匹配的释放函数,则将调用该函数;否则,将调用该函数。否则,将不调用任何释放函数。如果查找找到了通常的释放函数的两参数形式,并且该函数(被认为是放置释放函数)将被选择为分配函数的匹配项,则程序格式错误。 [示例:

struct S {
    // Placement allocation function:
    static void* operator new(std::size_t, std::size_t);
    // Usual (non-placement) deallocation function:
    static void operator delete(void*, std::size_t);
};

S* p = new (0) S; // ill-formed: non-placement deallocation function matches
                  // placement allocation function

最终示例]

返回[basic.stc.dynamic.deallocation]:

1解除分配功能应为类成员功能或全局功能;如果在全局范围以外的名称空间范围中声明了释放函数,或者在全局范围中声明了静态,则程序的格式不正确。

2每个释放函数应返回void,其第一个参数应为void*。一个释放函数可以具有多个参数。


1
投票

'placement new'不是new的重载版本,而是运算符new的变体之一,并且是一个不能重载的变体。

请参见可以看到新运算符here的列表以及有关如何重载它们的描述。

如果构造函数在使用placement new时引发异常,则编译器知道使用了哪个新运算符并调用placement delete。


0
投票

当作为new-expression]的一部分而构造的对象的构造失败时,将调用相应的释放函数-如果存在的话-将被调用。例如

new X;

将使用以下一对分配/解除分配功能。

void * operator new(std::size_t);
void operator delete(void *);

类似地,对于新形式的展示位置

new(&a) X;

将使用operator newoperator delete功能的放置版本。

void * operator new(std::size_t, void *);
void operator delete(void *, void *);

请注意,最后一个功能故意不执行任何操作。

© www.soinside.com 2019 - 2024. All rights reserved.