new
表达式
当我们使用一条 new
表达式时:
string *sp = new string("hello, world");
string *arr = new string[10];
实际执行了三步操作:
第一步,new
表达式调用一个名为 operator new
(或者 operator new[]
)的标准库函数,该函数分配一块足够大的内存空间以便存储特定类型的对象(或者对象的数组);
第二步,编译器运行相应的构造函数以构造这些对象,并为其传入初始值;
第三步,对象被构造完成,返回一个指向该对象的指针。
- 重载
operator new()
函数
如果应用程序希望控制内存分配的过程中,比如从一个内存池中分配内存,而不是通过系统调用来分配内存,则可以重载 operator new()
函数。其中,operator new()
的第一个参数(可能有多个参数,后面再讲)是size_t
,由new
表达式负责传入具体的值。下面是在一个类中重载operator new()
函数的示例:
#include <iostream>
#include <string>
#include <cstdlib>
class Foo
{
public:
Foo(const char *ptr)
{
std::cout << "Foo()" << std::endl;
str = ptr;
}
void *operator new(size_t size)
{
std::cout << "Foo::operator new(): input_size = " << size << std::endl;
std::cout << "sizeof Foo: " << sizeof(Foo) << std::endl;
void *ret = malloc(size);
std::cout << "malloced addr: " << ret << std::endl;
return ret;
}
void show(void)
{
std::cout << str << std::endl;
}
private:
std::string str;
};
int main(void)
{
Foo *f = new Foo("hello, world");
std::cout << "object addr: " << f << std::endl;
f->show();
return 0;
}
程序运行结果如下所示:
$ g++ -Wall -lpthread -std=c++11 main.cpp -o main -O3
$ ./main
Foo::operator new(): input_size = 24
sizeof Foo: 24
malloced addr: 0x7fdea8400640
Foo()
object addr: 0x7fdea8400640
hello, world
- 定位
new
基本用法
定位 new
也即是 placement new
,它的基本用法是:
new (placement-args ) type new-initializer
其中,placement-args
是复数形式,也就是说可以有多个参数,这些参数会被放到operator new()
的第一个参数后面。
具有特定参数列表的operator new()
需要由用户显式定义,示例如下所示:
#include <iostream>
#include <cstdlib>
void *operator new(size_t size, size_t ext1, size_t ext2)
{
std::cout << size << ", " << ext1 << ", " << ext2 << std::endl;
return malloc(size + ext1 + ext2);
}
struct node {
int val;
struct node *next;
int arr[0];
};
int main(void)
{
struct node *n = (struct node *)new (4, 8) int;
n->val = 1;
n->next = NULL;
n->arr[0] = 1;
return 0;
}
程序运行结果如下所示:
$ g++ -Wall -lpthread -std=c++11 main.cpp -o main -O3
$ ./main
operator new(): 4, 4, 8
- 定位
new
的特殊形式
定位new
有一种特殊形式,应用程序不能重载:
new (void* ) type new-initializer
也就是说,当 placement-args
是一个 指针类型 的形参时,不能自定义对应的 operator new()
函数,因为系统库已经定义了 void *operator new(size_t, void*)
函数,不允许重载,否则将会报错。
下面的代码尝试重载该函数:
#include <iostream>
void *operator new(size_t size, void *p)
{
return malloc(size);
}
int main(void)
{
int a = 2024;
int *b = new (&a) int;
std::cout << *b << std::endl;
return 0;
}
程序编译结果如下所示:
$ g++ -Wall -lpthread -std=c++11 main.cpp -o main -O3
main.cpp:3:7: error: 'operator new' is missing exception specification 'noexcept'
void *operator new(size_t size, void *p)
^
noexcept
/Library/Developer/CommandLineTools/usr/include/c++/v1/new:219:70: note: previous declaration is here
_LIBCPP_NODISCARD_AFTER_CXX17 inline _LIBCPP_INLINE_VISIBILITY void* operator new (std::size_t, void* __p) _NOEXCEPT {ret...
^
- 定位
new
特殊形式的用法
上面说的void *operator new(size_t, void*)
函数有特殊的用途,该函数不分配任何内存,它只是简单地返回 指针实参;然后由 new
表达式负责在指定的地址上初始化对象以完成整个工作。事实上,上述形式的定位 new
允许我们在一个特定的、预先分配的内存地址上构造对象。下面是上述定位 new
的一个使用示例:
#include <iostream>
class Foo
{
public:
Foo(int num)
{
std::cout << "Foo()" << std::endl;
val_ = num;
}
int val_;
};
int main(void)
{
void *addr = malloc(sizeof(Foo));
std::cout << addr << std::endl;
Foo *f = new (addr) Foo(2024);
std::cout << f << std::endl;
std::cout << f->val_ << std::endl;
return 0;
}
程序运行结果如下所示:
$ g++ -Wall -lpthread -std=c++11 main.cpp -o main -O3
$ ./main
0x7fea294006a0
Foo()
0x7fea294006a0
2024
参考资料:
- C++ primer(第五版)中文版。