1. new运算符和operator new()
new: 指我们在C++里通常用到的关键字。比如 A *a=new A,为了实现这一目的,应用程序需要重载new运算符和delete运算符以控制内存分配的过程。对于new来说,有new和::new之分,前者位于std命名空间中
operator new(或operator new[]):是一个标准库函数,并不是运算符。对于operator new 来说,分为全局重载和类重载,全局重载是void* ::operator new(size_t size),在类中重载形式void* A::operator new(size_t size)。还要注意的是这里的operator new()完成的操作一般只是分配内存,事实上系统默认的全局重载也只是调用malloc分配内存,并且返回一个void* 指针。
2.new 和 operator new之间的联系
(1)A* a=new A ; 这行代码实际执行三步操作:
1)new表达式调用一个名为operator new(或者operator new[])的标准库函数。该函数分配一块足够大的、原始的、未命名的内存空间以便存储A类型的对象(或者对象数组);
2)编译器运行相应地构造函数以构造这些对象,并为其传入初始值;
3)对象被分配了空间并构造完成,返回一个指向该对象的指针
事实上,如果类A重载了operator new,那么将调用A::operator new(size_t size),如果没有重载,就调用全局函数::operator new(size_t size),全局new操作符由c++默认提供。
(2)operator new接口和operator delete接口
标准库定义了以下8个版本。其中前4个版本可能抛出bad_alloc异常,后4个版本不会抛出异常;
//这些版本可能抛出异常
void* operator new (size_t);
void* operator new[] (size_t);
void* operator delete (void*) noexcept;
void* operator delete[] (void*) noexcept;
void* operator new (size_t, nothrow_t&) noexcept;
void* operator new[] (size_t, nothrow_t&) noexcept;
void* operator delete (void*, nothrow_t&) noexcept;
void* operator delete[] (void*,nothrow_t&) noexcept;
对于operator new函数:要实现不同的内存分配行为,应该重载operator new,而不是new。
operator new就像operator + 一样,是可以重载的。如果类中没有重载operator new,那么调用的就是全局的::operator new 来完成堆的内存申请。同理,operator new[]、operator delete、operator delete[]也是可以重载的。
尽管operator new函数和operator delete函数一般用于new表达式,然而它们毕竟是标准库的两个普通函数,因此普通的代码也可以直接调用它们。
3. ::new与new
默认情况下编译器会将new这个关键字翻译成全局::operator new和相应的构造函数。
但在有的情况下,用户自己会在类中重载operator new。这种情况下,编译器默认会使用类中重载的operator new(本质上因为编译器会从命名空间由内向外查找自己想要的函数,选用第一个)。
如果我们想要继续使用默认的operator new,就应该写成::new ,字面意思是调用最外层命名空间中的operator new。
值得一提的是,全局operator new也是可以被重载的。通过这种方式,我们可以改变所有new的部分行为。
4. placemen new 定位new表达式
功能:
定位new允许我们在一个特定的、预先分配的内存地址上构造对象,实参不局限于动态内存。
形式:
new ( place_address ) type
new ( place_address ) type (initializers)
new ( place_address ) type [size]
new ( place_address ) type [size] { braced initializer list }
其中place_address必须是一个指针,同时在initializers中提供了一个(可能为空的)以逗号分隔的初始值列表,该初始值列表将用于构造新分配的对象。
1)当仅通过一个地址值调用时,定位new使用operator new (size_t, void*)“分配”它的内存。这是一个我们无法重载的operator new版本。该函数不分配任何内存,它只是简单地返回指针实参;然后由new表达式负责在指定的地址初始化对象以完成整个工作。
eg. char* ptr = new char[ sizeof(T) ]; //allocate memory
T* tptr = new(ptr) T; //construct in allocated storage ("place")
tptr->~T(); //destruct
delete[] ptr; //deallocate memory
2) 如果不是1)情况,则表达式返回place_address地址。
(C++ Primer 第五版 P753)
eg. Token &Token::operator=( const std::string &s)
{
if ( tok ==STR) //如果当前存储的是string,可以直接赋值
sval = s;
else
new(&sval) string(s); //否则需要先构造一个string
tok = STR; //更新判别式
return *this;
}
可以看出,通过定位new表达式,我们可以实现保存一块内存,反复构造析构,这样可以省略中间的多次分配内存。由于malloc内存会导致系统调用,这可以节省大量的系统开销。