我看到以下结构:
[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
时如何清理内存?
[当构造函数引发异常时,将调用匹配的delete。不会为抛出的类调用析构函数,但是成功调用了其构造函数的类的任何组件都将调用其析构函数。
根本上,如果没有与delete
运算符相对应的new
运算符,则不执行任何操作。在新放置的情况下也不会执行任何操作,因为相应的放置删除操作符是no-op。异常不会被转移:它会继续前进,因此新调用者有机会(和负责)释放分配的内存。
称为newplacement,因为它用于将对象放置在以其他方式获取的内存中;由于内存不是由新操作员获取的,因此删除操作不太可能释放该内存。实际上,这个问题是有争议的,因为(至少从C ++ 03起)不允许替换placement new运算符(其原型为operator new(size_t, void*)
或删除(operator delete(void*, void*)
)。提供的placement new运算符返回其第二个参数,并且提供的展示位置删除运算符为无操作。
其他new
和delete
运算符可以全局替换或特定类替换。如果调用了自定义new
运算符,并且构造函数抛出异常,并且存在相应的delete
运算符,则将在删除该异常之前传播该delete运算符以进行清理。但是,如果没有相应的delete
运算符,这不是错误。
首先,一个例子:
#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-expression的expression-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*
。一个释放函数可以具有多个参数。
'placement new'不是new的重载版本,而是运算符new的变体之一,并且是一个不能重载的变体。
请参见可以看到新运算符here的列表以及有关如何重载它们的描述。
如果构造函数在使用placement new时引发异常,则编译器知道使用了哪个新运算符并调用placement delete。
当作为new-expression]的一部分而构造的对象的构造失败时,将调用相应的释放函数-如果存在的话-将被调用。例如
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 *);
请注意,最后一个功能故意不执行任何操作。