13

我看到以下构造:

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

  • operator new()可以超载。

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

最常见的 operator new 重载是placement new,即void *operator new(void *p) { return p; }

您几乎总是无法调用delete给定位置的指针new

X这就引出了一个问题:当构造函数抛出并使用重载时,如何清理内存new

4

5 回答 5

5

从根本上说,如果没有delete对应于操作符的new操作符,则什么也不做。在放置 new 的情况下也不做任何事情,因为相应的放置删除操作符是空操作。异常没有被转移:它继续它的过程,所以 new 的调用者有机会(和责任)释放分配的内存。

Placement new 之所以被称为是因为它用于将对象放置在内存中,否则会获得;由于内存不是由 new 运算符获取的,因此它不太可能被 delete 运算符释放。实际上,这个问题没有实际意义,因为(至少从 C++03 开始​​)不允许替换放置新运算符(具有原型operator new(size_t, void*)或删除 ( operator delete(void*, void*))。提供的放置新运算符返回其第二个参数,并且提供的放置删除运算符是无操作的。

可以全局或针对特定类替换其他new和运算符。delete如果new调用了自定义算子,构造函数抛出异常,并且有对应的delete算子,那么在传播异常之前会调用那个delete算子进行清理。但是,如果没有相应的delete运算符,则不是错误。

于 2013-10-30T15:51:01.500 回答
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
例外:向上
无参数释放

运算符 new 重载的规范定义是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)这里被概括为一个表达式列表。特定新表达式表达式列表中的每个表达式都作为附加参数传递给分配函数。如果释放函数被调用(例如,因为被调用的 ctor 抛出异常),相同的参数加上前导(分配函数的返回值)被传递给释放函数。void*

[expr.new]/18 指出:

如果上述对象初始化的任何部分因抛出异常而终止,则已为该对象获得存储空间,并且可以找到合适的释放函数,则调用释放函数以释放构造对象的内存,之后异常继续在new-expression的上下文中传播。如果找不到明确匹配的解除分配函数,则传播异常不会导致对象的内存被释放。[注:这适用于被调用的分配函数不分配内存的情况;否则,很可能导致内存泄漏。——尾注]

和 /21

如果一个new 表达式调用一个释放函数,它会将分配函数调用返回的值作为 type 的第一个参数传递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*. 一个释放函数可以有多个参数。

于 2013-10-30T16:36:16.427 回答
1

当构造函数抛出异常时,将调用匹配的删除。不会为抛出的类调用析构函数,但该类中已成功调用其构造函数的任何组件都将调用其析构函数。

于 2013-10-30T15:50:21.363 回答
1

'placement new' 不是 new 的重载版本,而是 operator new 的变体之一,也是不能重载的变体。

请参阅此处的新运算符列表以及有关重载它们如何工作的说明。

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

于 2013-10-30T15:51:53.380 回答
0

当作为new 表达式的一部分构造的对象的构造失败时,将调用相应的释放函数(如果有的话)。例如

new X;

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

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

同样,对于表单的新展示位置

new(&a) X;

operator new将使用和operator delete函数的放置版本。

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

请注意,最后一个函数有意不执行任何操作。

于 2013-10-30T15:53:48.497 回答