C++关键字-new
new在C++中主要有3种用法:
- new operator:也叫做new表达式,它调用operator new进行内存分配,并调用构造函数初始化对象。
- placement new:是一个特殊的new表达式,它不进行内存分配,而是在已分配好的内存上调用构造函数初始化对象。
- operator new: 是一个全局函数,它只负责内存分配。
new表达式
new表达式(也叫new运算符)负责三件事:
- 调用适当的内存分配函数(operator new,返回 void*)。
- 在分配到的内存上构造对象(调用构造函数)。
- 返回对象的指针。
new T和new T()
内置类型:
// 申请内存并进行默认初始化,a的值未定义
int* a = new int;
// 申请内存并进行值初始化,b的值为0
int* b = new int();
// 申请内存并进行值初始化,c的值为1
int* c = new int(1);
自定义类型:
class Foo;
// 申请内存并进行默认初始化,调用Foo的默认构造函数
Foo* f1 = new Foo;
// 申请内存并进行值初始化,调用Foo的默认构造函数
Foo* f2 = new Foo();
new[]
数组形式的new T[n]会为n个T分配数组空间,并逐一调用T的默认构造函数(或值初始化)。
内置类型:
// 申请内存并进行默认初始化,a的所有元素值都未定义
int* a = new int[10];
// 申请内存并进行值初始化,a的所有元素值都为0
int* b = new int[10]();
// 申请内存并进行值初始化,前三个元素为1,其余元素为0
int* c = new int[10]{1, 1, 1};
自定义类型:
// 申请内存并进行默认初始化,f1的所有元素值都调用默认构造函数
Foo* f1 = new Foo[3];
// 申请内存并进行值初始化,f2的所有元素值都调用默认构造函数
Foo* f2 = new Foo[3]{Foo(), Foo(), Foo()};
注意:new[]和delete[]必须配对使用
- 当使用new[n]申请内存时,编译器会将数组长度n存储在这个内存的开头,并将内存首地址向后偏移sizeof(size_t),最终返回偏移后的指针。
- 当调用delete[]时,并不需要传递数组的大小,编译器将提供的指针向前偏移sizeof(size_t),读取这个长度,并依次对后面每个元素调用析构函数。
nothrow new
new表达式默认在分配内存失败时会抛出std::bad_alloc异常。
但可以用nothrow形式避免抛出异常,而是返回nullptr:
int* p = new (std::nothrow) int;
if (!p) {
    /* 处理分配失败 */
}
new和malloc的区别
- new表达式内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换;而malloc内存分配成功则是返回void*,需要强制类型转换
- new表达式内存分配失败时,会抛出std::bad_alloc异常,;而malloc分配内存失败时返回NULL。
- new表达式申请内存分配时无须指定内存块的字节大小,编译器会根据类型信息自行计算;而malloc则需要显式地指出所需内存的字节大小。
- new表达式会调用对象的构造函数以完成对象的构造;而malloc则不会。
placement new
placement new主要作用不是分配内存,而是在已经提供的一块内存上构建一个对象。
placement new是一个特殊的new表达式,它使用特殊的operator new函数来实现。
void* operator new  ( std::size_t size, void* ptr );
void* operator new[]( std::size_t size, void* ptr );
这个特殊的operator new函数的目的就是跳过内存分配这一步。它不关心size,也不向申请新的内存,只是将你传入的ptr原样返回。
class Foo;
int main()
{
    void* buffer = malloc(sizeof(Foo));
    assert(buffer != NULL);
    // 使用placement new在buffer这块内存上构造Foo对象。
    // new (buffer) Foo()会调用Foo的构造函数,
    Foo* f = new (buffer) Foo();
    // 重点:不会自动自动析构,必须手动调用析构函数
    f->~Foo();
    free(buffer);
}
当执行new (buffer) Foo(10)时:
- 调用operator new函数:operator new(sizeof(Foo), buffer),并返回buffer。
- 调用Foo的构造函数:在buffer上构造对象并返回。
operator new
operator new是系统提供的全局函数,它执行的操作只是分配内存。
事实上::operator new通常只是调用malloc分配内存,并且返回一个void*指针。
默认的operator new
分配失败抛出异常的版本:
void* operator new  ( std::size_t ) (std::bad_alloc);
void* operator new[]( std::size_t ) (std::bad_alloc);
// C++17引入对齐分配
void* operator new  ( std::size_t, std::align_val_t ) (std::bad_alloc);
void* operator new[]( std::size_t, std::align_val_t ) (std::bad_alloc);
分配失败不抛出异常的版本:
void* operator new  ( std::size_t, const std::nothrow_t& );
void* operator new[]( std::size_t, const std::nothrow_t& );
// C++17引入对齐分配
void* operator new  ( std::size_t, std::align_val_t, const std::nothrow_t& ) noexcept;
void* operator new[]( std::size_t, std::align_val_t, const std::nothrow_t& ) noexcept;
重载operator new
重载operator new时:
- 返回类型必须声明为void*
- 第一个参数类型必须为表达要求分配空间的大小,类型为size_t
- 可以带其它参数
void* operator new  ( std::size_t size, /* args... */ );
void* operator new[]( std::size_t size, /* args... */ );
// C++17引入对齐分配
void* operator new  ( std::size_t size, std::align_val_t al, /* args... */ );
void* operator new[]( std::size_t size, std::align_val_t al, /* args... */ );
全局重载
全局重载operator new会影响程序中所有使用new关键字的地方。除非你明确指定了其他内存分配器。
类内重载
如果类中没有重载operator new,那么调用的就是全局的::operator new。
如果在类中重载operator new,只会影响该类的对象以及其派生类的对象(除非派生类自己重载了)。
类内的operator new默认是静态成员函数,但不需要使用static关键字显式声明。
new的一些用法
如何限制对象只能建立在堆上:设置析构函数为protected
class A {
protected:
    A(){}
    ~A(){}
public:
    static A* create()
    {
        return new A();
    }
    void destory()
    {
        delete this;
    }
};
如何限制对象只能建立在栈上:重载operator new并设为private
class A {
private:
    void* operator new(size_t t){}
    void operator delete(void* ptr){}
public:
    A(){}
    ~A(){}
};