改写placement new去掉外层括号是否会编译通过但改变程序语义
背景
- 正在清理遗留代码库,落实针对
new语句的编码规范。 - 代码中存在形如
auto x = new(ClassName);的写法,已将其改写为auto x = new ClassName();,这类写法不属于placement new,无需额外考量。 - 代码中还存在形如
auto x = new(ClassName)(argument);的写法,外观与placement new高度相似,目前已直接将这类代码统一改写为auto x = new ClassName(argument);。
问题
是否存在如下场景:真正的placement new写法auto x = new(placement-params)(Type);被改写为auto x = new placement-params(Type);后,代码仍可正常编译,但程序的实际语义发生了改变?
回答
确实存在这类场景,属于C++语法规则导致的天然歧义,批量替换时非常容易踩坑。
核心原因是C++允许同一作用域内存在同名的类型和变量,而new表达式的语法解析会严格根据语境判断标识符的含义:new后括号内是表达式语境,会优先匹配同名变量/对象;new后直接跟随的标识符是类型语境,会优先匹配同名类型。
最典型的可复现场景如下:
#include <new> #include <cstdio> // 定义类型TestObj struct TestObj { TestObj() = default; // 存在接收void*参数的构造函数 TestObj(void*) { puts("普通new:在堆上分配TestObj内存,调用单参构造函数"); } }; // 定义同名变量,是指向栈缓冲区的void*指针 char stack_buf[sizeof(TestObj)]; void* TestObj = stack_buf; int main() { // 原写法:合法的placement new // 第一个括号内是表达式语境,TestObj解析为void*指针变量,作为placement参数 // 第二个括号内是类型语境,TestObj解析为struct TestObj类型 // 语义:在栈缓冲区stack_buf上默认构造TestObj,无堆分配 auto p1 = new(TestObj)(TestObj); // 改写后的写法:合法的普通new // new后直接跟TestObj是类型语境,解析为struct TestObj类型 // 括号内的TestObj是表达式语境,解析为void*指针变量,作为构造参数 // 语义:在堆上申请TestObj大小的内存,调用单参构造函数传入指针,返回堆地址 auto p2 = new TestObj(TestObj); }
这两段代码都可以正常编译运行,但语义完全不同:
- 原placement new不会申请堆内存,直接在预先准备好的缓冲区上构造对象,返回的指针不能直接调用
delete,需要手动调用析构函数后释放对应缓冲区。 - 改写后的普通new会向运行时申请堆内存,返回的指针必须调用
delete释放,否则会出现内存泄漏。
除此之外,当placement参数是临时对象、且该临时对象的类型名与目标构造类型存在同名的重载构造/重载operator new时,也可能出现改写后语法合法但语义完全偏移的情况,甚至触发未定义行为。
清理这类代码时不要直接靠正则批量替换,判断逻辑非常简单:逐个检查new后第一个括号内的内容,如果内容可以被解析为当前作用域下的合法类型名,那就是老代码给类型加了多余括号,可以安全改写为普通new写法;如果括号内是变量名、函数调用、临时对象表达式等内容,那就是真正的placement new,绝对不能去掉括号改写。
内容的提问来源于stack exchange,提问作者Thomas W.
相关产品推荐
相关产品推荐

