C++ C++;当构造函数抛出异常并使用自定义新值时释放内存

C++ C++;当构造函数抛出异常并使用自定义新值时释放内存,c++,exception,new-operator,language-lawyer,C++,Exception,New Operator,Language Lawyer,我看到以下结构: new X将在X构造函数抛出时释放内存 运算符new()可以重载 操作符new重载的规范定义是void*操作符new(size\u t c,heap h)和相应的操作符delete 最常见的运算符new重载是placement new,它是void*operator new(void*p){return p;} 对于指定给placementnew的指针,几乎总是无法调用delete 这就引出了一个问题:当X构造函数抛出并使用重载的new时,如何清理内存?当构造函数抛出异常时

我看到以下结构:

  • new X
    将在
    X
    构造函数抛出时释放内存

  • 运算符new()
    可以重载

操作符new重载的规范定义是
void*操作符new(size\u t c,heap h)
和相应的
操作符delete

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

对于指定给placement
new
的指针,几乎总是无法调用
delete


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

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

基本上,如果没有与
new
运算符对应的
delete
运算符,则不会执行任何操作。在placement new的情况下,也不会执行任何操作,因为相应的placement delete运算符是no-op。异常不会转移:它会继续其过程,因此new的调用方有机会(和责任)释放分配的内存

Placement new之所以称之为Placement new,是因为它用于将对象放入以其他方式获取的内存中;由于新操作员未获取内存,因此删除操作员不太可能释放内存。在实践中,这个问题是没有意义的,因为(至少从C++03开始)不允许替换placement new操作符(它具有原型
操作符new(size,void*)
或delete(
操作符delete(void*,void*)
)。提供的placement new运算符返回其第二个参数,而提供的placement delete运算符为no-op


其他
new
delete
运算符可以全局替换,也可以针对特定类替换。如果调用了自定义
new
运算符,并且构造函数引发异常,并且存在相应的
delete
运算符,则将调用该delete运算符以在异常发生之前进行清理门控。但是,如果没有相应的
delete
运算符,则不是错误。

“placement new”不是new的重载版本,而是new运算符的变体之一,也是不能重载的

可以查看新运算符的列表以及重载运算符的工作方式说明


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

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

new X;
将使用以下一对分配/解除分配函数

void * operator new(std::size_t);
void operator delete(void *);
同样,对于表单的新位置

new(&a) X;
将使用
操作员新建
操作员删除
功能的放置版本

void * operator new(std::size_t, void *);
void operator delete(void *, void *);
请注意,最后一个函数故意不执行任何操作。

首先,举个例子:

#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;
}
-[结束示例]

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

1解除分配函数应为类成员函数或全局函数;如果解除分配函数在非全局范围的命名空间范围内声明或在全局范围内声明为静态,则程序的格式不正确

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


重载<代码>新< /C> >放置新品种?您应该注意C++标准(至少C++ 03)不允许程序重载新的位置。@ John Dibling:去读新的。我不确定我看到你的观点。那个文件是C++标准库的一个实现,或者它是不符合的。要点是编译器必须定义能够编译C++中的标准库的部分,如果不林,则可以扩展。k你可以替换它。这是对Wikipedia页面的误读,而这又有一些不准确之处。Wikipedia页面中覆盖的new/delete对的示例只是一个示例;你可以使用任何附加参数类型序列(除了保留的void*)。我怀疑Wikipedia示例的意图是类型A将是某种分配器。在任何情况下,如果您愿意,您都可以调用默认的::delete(void*,void*),但它不会起任何作用。 allocated with arguments: 1, 2, 3 allocated with arguments: 4, 5, 6 deallocated with arguments: 4, 5, 6 exception: up deallocated w/o arguments
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