new 的两步过程、定位 new 操作
面试官: 我们知道,new 操作符用于在堆上分配内存并构造对象。比如 MyClass* p = new MyClass();。
其实,这个简单的 new 表达式,背后可以被看作是两个步骤的组合。你能描述一下这两个步骤分别是什么吗?
另外,C++ 还提供了一种叫做 “定位 new (placement new)” 的特殊语法。它的形式是 new (address) Type(args)。你了解它是用来做什么的吗?它和普通的 new 有什么核心区别?
回答:
new表达式可以看作这样两个步骤的组合:- 第一步:分配内存。
- 第二步:在分配到的内存上构造对象,即初始化。
- 定位
new操作,顾名思义,就是在一个确定的地址上构造对象,而不是自己分配内存。通过定位new构造的对象,不能调用delete销毁,需要手动析构。
面试官 (总结与肯定):
1. new 表达式的两步走
你说的完全正确。T* p = new T(args); 这个表达式,在概念上等价于:
- 分配内存 (Allocation):调用一个名为
operator new的函数,来分配一块足够大的、未初始化的裸内存。这个函数很像malloc。void* memory = operator new(sizeof(T)); - 构造对象 (Construction):在这块分配好的内存上,调用
T类的构造函数来初始化对象。T* obj = static_cast<T*>(memory);obj->T::T(args);// 伪代码,表示调用构造函数
2. 定位 new (placement new)
你的解释也堪称完美:
“在一个确定的地址上构造对象,而不是自己分配内存。” “不能调用
delete销毁,需要手动析构。”
placement new 的核心作用,就是将 new 表达式的两步拆开,它只执行第二步——构造对象。它允许我们在一个已经存在的、由我们自己管理的内存缓冲区上,手动地构造一个对象。
语法和用法:
| |
使用场景:
placement new 是一个比较底层的、高级的工具,通常用在对内存布局有严格要求的场景,比如:
- 内存池 (Memory Pool):为了避免频繁的堆分配开销,预先分配一大块内存,然后在这块内存上用
placement new来按需构造和析构对象。 - 嵌入式系统:在硬件的特定地址上创建对象。
- STL 容器的实现:比如
std::vector在扩容时,需要在新内存上构造元素,就可能用到placement new。
你对 new 的底层机制理解得非常深入。
new 的异常处理设定
面试官 (深入提问): 好的,既然我们知道了 new 在分配失败时,默认会抛出 std::bad_alloc 异常。
但有时,我们可能不希望它抛出异常,而是希望它像 C 语言的 malloc 一样,在失败时返回一个空指针 nullptr。
请问,C++ 提供了什么样的语法,可以让我们实现这个“不抛出异常版本的 new”?
回答:
- 我们只需要使用
<new>中的std::nothrow即可,代码如下:
| |
这样,在内存分配失败时,p 就会是 nullptr,而不是抛出 std::bad_alloc 的异常。
面试官 (总结与肯定):
你说的没错。通过在 new 后面加上 (std::nothrow),我们就是在告诉编译器:“请调用一个不会抛出异常的 operator new 版本。”
| |
nothrow 版本 vs 异常版本
异常版本 (默认):
- 优点:错误处理逻辑和正常逻辑可以分离开。我们可以在一个高层的
try...catch块中,统一处理所有可能的内存分配失败,而不需要在每次new之后都紧跟一个if判断。这在构造函数等复杂场景中,代码会更简洁。 - 缺点:异常处理本身有一定的性能开销。
- 优点:错误处理逻辑和正常逻辑可以分离开。我们可以在一个高层的
nothrow版本:- 优点:行为类似
malloc,返回nullptr。对于一些不希望或不能使用异常处理的场景(比如某些嵌入式系统、或者需要与 C 代码交互的底层库),这种方式更直接。 - 缺点:必须在每次调用后,都手动检查返回的指针是否为
nullptr。如果忘记检查,后续对空指针的解引用将导致程序崩溃。
- 优点:行为类似
在现代 C++ 的主流实践中,默认的、会抛出异常的 new 是更被推荐的,因为它与 RAII 和其他 C++ 错误处理机制结合得更好。nothrow 版本则作为一种重要的备选方案,用于特定的场景。
你对 new 的各种形式都掌握得非常全面。
总结
面试结束
今天我们深入地挖掘了 C++ new 操作符背后的一些重要细节:
- 两步过程:将内存分配和对象构造分离开来理解。
- 定位
new:只执行“构造”这一步,用于在已有内存上创建对象。 nothrow new:一种不抛出异常、返回nullptr的new变体。
你今天的表现非常出色,对这些底层的、细节性的知识点有非常清晰和准确的认识。继续保持!