C++ placement new 如何工作?
How C++ placement new works?
这个问题是为了确认我对概念的理解是否正确,并就用法风格和可能的优化征求专家意见。
我正在努力理解 "placement new" 下面是我想出的程序...
#include <iostream>
#include <new>
class A {
int *_a;
public:
A(int v) {std::cout<<"A c'tor clalled\n";_a= new int(v);}
~A() {std::cout<<"A d'tor clalled\n"; delete(_a);}
void testFunction() {std::cout<<"I am a test function &_a = "<<_a<<" a = "<<*_a<<"\n";}
};
int main()
{
A *obj1 = new A(21);
std::cout<<"Object allocated at "<<obj1<<std::endl;
obj1->~A();
std::cout<<"Object allocated at "<<obj1<<std::endl;
obj1->testFunction();
A *obj2 = new(obj1) A(22);
obj1->testFunction();
obj2->testFunction();
delete(obj1);// Is it really needed now? Here it will delete both objects.. so this is not the right place.
//obj1->testFunction();
//obj2->testFunction();
return 0;
}
当我 运行 这个程序时,我得到了关注 o/p
A c'tor clalled
Object allocated at 0x7f83eb404c30
A d'tor clalled
Object allocated at 0x7f83eb404c30
I am a test function &_a = 0x7f83eb404c40 a = 21
A c'tor clalled
I am a test function &_a = 0x7f83eb404c40 a = 22
I am a test function &_a = 0x7f83eb404c40 a = 22
A d'tor clalled
I am a test function &_a = 0x7f83eb404c40 a = 0
I am a test function &_a = 0x7f83eb404c40 a = 0
我有以下问题...
- 演示放置新的示例是否正确?
- 成员 a 是动态分配的(没有放置新的)。那么为什么它为 obj1 和 obj2 获得相同的地址。这只是巧合吗?
- D'tor 在 15 号线打电话是个好习惯吗?
还请指出您看到的任何我可以改进的地方,或者只是不要尝试。也欢迎任何好的参考或阅读。
这可能是 CodeReview.SE 的内容,让我在回答您的问题之前稍微评论一下您的源代码。
A *obj1 = new A(21);
std::cout<<"Object allocated at "<<obj1<<std::endl;
obj1->~A();
您通常不会对 不是 使用 placement-new 创建的对象调用析构函数。在你的例子中,你破坏了旧的并构建了一个新的放置新的。即使这有效,您也应该实现一些 reset 功能来重置您的对象,而不是破坏和构建一个新对象。
17 obj1->testFunction();
这是UB。您已经破坏了该对象,您不应该对其调用任何方法。
18 A *obj2 = new(obj1) A(22);
19 obj1->testFunction();
20 obj2->testFunction();
这是还可以,请注意obj1
和obj2
是完全相同的对象。
21 delete(obj1);// Is it really needed now? Here it will delete both objects.. so this is not the right place.
您的评论有误。您不是在删除两个对象,而是在删除一个,稍后再删除。
22 obj1->testFunction();
23 obj2->testFunction();
这又是 UB,不要在解构或删除的对象上调用方法。
针对您的问题:
member _a is dynamically allocated (with no placement new). So why it is getting same same address for obj1 & obj2. Is it just a coincidence?
不要称它们为 obj1
和 obj2
,因为这两个变量指向同一个对象,但是,是的,这是巧合。在第一个对象被破坏并释放了这个内存之后,第二个对象分配了刚刚释放的相同数量的内存,分配器决定给你完全相同的内存。
is D'tor call on line 15 a good practice?
不,不是。为什么需要调用析构函数的例子很少,其中之一就是你的对象是由 placement-new 创建的。在您的示例中,这没有副作用,因为您在解构旧对象后在同一位置构造了一个新对象,并且新对象与旧对象具有相同的类型,否则这可能会以某种方式严重破坏。
现在详细介绍一下您删除后的评论。让我们看看 new
和 placement-new 的实际作用。
一个新的:
- 为新对象OS分配内存
- 在新对象上调用构造函数,地址 (
this
) 设置为分配器获得的内存块。
删除则相反:
- 调用对象的析构函数
- 释放内存块
现在到 placement-new:placement-new 只是跳过第一步(分配内存)并调用 new 对象的构造函数 this
到你传递的地址。因此,placement-new 的opposite 只是调用析构函数,因为不存在 placement-delete。
这意味着对于您的代码,在您调用析构函数之后,您的第一个对象 死亡 但您从未归还内存,这就是为什么您可以在该内存中构造一个新对象.现在,当您调用 delete 时,第一个对象不再存在,只存在它使用的内存,但相同的内存现在被第二个对象阻塞,因此当您调用 delete 时,您不会删除两个对象,您只删除第二个一个(你解构它然后释放内存块)。
您可以在 isocpp's faq
阅读更多关于主题 placement-new 和何时调用析构函数的信息
真的非常简单:new
可以认为是做两件事:
- 正在分配内存。
- Placement——在分配的内存中构造对象。
不能保证 malloc
确实被实现所使用,但通常是这样。你不能假设它的实现,但为了理解它是一个 OK 假设。
因此,以下内容被认为是等价的:
auto obj1 = new std::string("1");
// ↑ can be thought of as equivalent to ↓
auto obj2 = (std::string*)malloc(sizeof(std::string));
new(obj2) std::string("2");
delete
也是如此:
delete obj1;
// ↑ can be thought of as equivalent to ↓
obj2->~string();
free(obj2);
然后,当您看到 new
和 delete
它们的真正含义时,您就可以轻松地对这一切进行推理:分配后跟构造函数调用,以及析构函数调用后跟释放。
当您使用展示位置 new
时,您已决定单独处理第一步。内存仍然必须以某种方式分配,您只需完全控制它是如何发生的以及内存来自哪里。
因此,您必须分别跟踪两件事:
内存的生命周期。
对象的生命周期。
下面的代码演示了它们是如何相互独立的:
#include <cstdlib>
#include <string>
#include <new>
using std::string;
int main() {
auto obj = (string*)malloc(sizeof(string)); // memory is allocated
new(obj) string("1"); // string("1") is constructed
obj->~string (); // string("1") is destructed
new(obj) string("2"); // string("2") is constructed
obj->~string (); // string("2") is destructed
free(obj); // memory is deallocated
}
如果对象的生命周期超过了内存的生命周期,你的程序就会有 UB。确保内存 always 比对象的寿命长。例如,这有 UB:
void ub() {
alignas(string) char buf[sizeof(string)]; // memory is allocated
new(buf) string("1"); // string("1") is constructed
} // memory is deallocated but string("1") outlives the memory!
但这没关系:
void ub() {
alignas(string) char buf[sizeof(string)]; // memory is allocated
new(buf) string("1"); // string("1") is constructed
buf->~string(); // string("1") is destructed
} // memory is deallocated
请注意如何使用 alignas
正确对齐自动缓冲区。任意类型缺少 alignas
会导致 UB。它可能看起来有效,但这只会误导您。
有一些特定的类型,不调用析构函数和不正确对齐内存不会导致 UB,但你永远不应该对类型做出这样的假设。调用你的析构函数并进行对齐,如果它被证明是不必要的,它不会花费你任何东西 - 不会为这种类型生成额外的代码。
struct S {
char str[10];
}
How C++ placement new works?
...
I am trying to understand "placement new" and following is the program I came up with...
两个答案都很好。但是你也想知道它是如何工作的,所以,我将从汇编中添加解释:
A *obj1 = new A(21);
:
call operator new(unsigned long)
mov esi, 21
mov rdi, rax
mov rbx, rax
call A::A(int)
A *obj2 = new(obj1) A(22);
mov esi, 22
mov rdi, rbx
call A::A(int)
这就是它的工作原理,已经很清楚了,不需要再解释了吧?
这个问题是为了确认我对概念的理解是否正确,并就用法风格和可能的优化征求专家意见。
我正在努力理解 "placement new" 下面是我想出的程序...
#include <iostream>
#include <new>
class A {
int *_a;
public:
A(int v) {std::cout<<"A c'tor clalled\n";_a= new int(v);}
~A() {std::cout<<"A d'tor clalled\n"; delete(_a);}
void testFunction() {std::cout<<"I am a test function &_a = "<<_a<<" a = "<<*_a<<"\n";}
};
int main()
{
A *obj1 = new A(21);
std::cout<<"Object allocated at "<<obj1<<std::endl;
obj1->~A();
std::cout<<"Object allocated at "<<obj1<<std::endl;
obj1->testFunction();
A *obj2 = new(obj1) A(22);
obj1->testFunction();
obj2->testFunction();
delete(obj1);// Is it really needed now? Here it will delete both objects.. so this is not the right place.
//obj1->testFunction();
//obj2->testFunction();
return 0;
}
当我 运行 这个程序时,我得到了关注 o/p
A c'tor clalled
Object allocated at 0x7f83eb404c30
A d'tor clalled
Object allocated at 0x7f83eb404c30
I am a test function &_a = 0x7f83eb404c40 a = 21
A c'tor clalled
I am a test function &_a = 0x7f83eb404c40 a = 22
I am a test function &_a = 0x7f83eb404c40 a = 22
A d'tor clalled
I am a test function &_a = 0x7f83eb404c40 a = 0
I am a test function &_a = 0x7f83eb404c40 a = 0
我有以下问题...
- 演示放置新的示例是否正确?
- 成员 a 是动态分配的(没有放置新的)。那么为什么它为 obj1 和 obj2 获得相同的地址。这只是巧合吗?
- D'tor 在 15 号线打电话是个好习惯吗?
还请指出您看到的任何我可以改进的地方,或者只是不要尝试。也欢迎任何好的参考或阅读。
这可能是 CodeReview.SE 的内容,让我在回答您的问题之前稍微评论一下您的源代码。
A *obj1 = new A(21);
std::cout<<"Object allocated at "<<obj1<<std::endl;
obj1->~A();
您通常不会对 不是 使用 placement-new 创建的对象调用析构函数。在你的例子中,你破坏了旧的并构建了一个新的放置新的。即使这有效,您也应该实现一些 reset 功能来重置您的对象,而不是破坏和构建一个新对象。
17 obj1->testFunction();
这是UB。您已经破坏了该对象,您不应该对其调用任何方法。
18 A *obj2 = new(obj1) A(22);
19 obj1->testFunction();
20 obj2->testFunction();
这是还可以,请注意obj1
和obj2
是完全相同的对象。
21 delete(obj1);// Is it really needed now? Here it will delete both objects.. so this is not the right place.
您的评论有误。您不是在删除两个对象,而是在删除一个,稍后再删除。
22 obj1->testFunction();
23 obj2->testFunction();
这又是 UB,不要在解构或删除的对象上调用方法。 针对您的问题:
member _a is dynamically allocated (with no placement new). So why it is getting same same address for obj1 & obj2. Is it just a coincidence?
不要称它们为 obj1
和 obj2
,因为这两个变量指向同一个对象,但是,是的,这是巧合。在第一个对象被破坏并释放了这个内存之后,第二个对象分配了刚刚释放的相同数量的内存,分配器决定给你完全相同的内存。
is D'tor call on line 15 a good practice?
不,不是。为什么需要调用析构函数的例子很少,其中之一就是你的对象是由 placement-new 创建的。在您的示例中,这没有副作用,因为您在解构旧对象后在同一位置构造了一个新对象,并且新对象与旧对象具有相同的类型,否则这可能会以某种方式严重破坏。
现在详细介绍一下您删除后的评论。让我们看看 new
和 placement-new 的实际作用。
一个新的:
- 为新对象OS分配内存
- 在新对象上调用构造函数,地址 (
this
) 设置为分配器获得的内存块。
删除则相反:
- 调用对象的析构函数
- 释放内存块
现在到 placement-new:placement-new 只是跳过第一步(分配内存)并调用 new 对象的构造函数 this
到你传递的地址。因此,placement-new 的opposite 只是调用析构函数,因为不存在 placement-delete。
这意味着对于您的代码,在您调用析构函数之后,您的第一个对象 死亡 但您从未归还内存,这就是为什么您可以在该内存中构造一个新对象.现在,当您调用 delete 时,第一个对象不再存在,只存在它使用的内存,但相同的内存现在被第二个对象阻塞,因此当您调用 delete 时,您不会删除两个对象,您只删除第二个一个(你解构它然后释放内存块)。
您可以在 isocpp's faq
阅读更多关于主题 placement-new 和何时调用析构函数的信息真的非常简单:new
可以认为是做两件事:
- 正在分配内存。
- Placement——在分配的内存中构造对象。
不能保证 malloc
确实被实现所使用,但通常是这样。你不能假设它的实现,但为了理解它是一个 OK 假设。
因此,以下内容被认为是等价的:
auto obj1 = new std::string("1");
// ↑ can be thought of as equivalent to ↓
auto obj2 = (std::string*)malloc(sizeof(std::string));
new(obj2) std::string("2");
delete
也是如此:
delete obj1;
// ↑ can be thought of as equivalent to ↓
obj2->~string();
free(obj2);
然后,当您看到 new
和 delete
它们的真正含义时,您就可以轻松地对这一切进行推理:分配后跟构造函数调用,以及析构函数调用后跟释放。
当您使用展示位置 new
时,您已决定单独处理第一步。内存仍然必须以某种方式分配,您只需完全控制它是如何发生的以及内存来自哪里。
因此,您必须分别跟踪两件事:
内存的生命周期。
对象的生命周期。
下面的代码演示了它们是如何相互独立的:
#include <cstdlib>
#include <string>
#include <new>
using std::string;
int main() {
auto obj = (string*)malloc(sizeof(string)); // memory is allocated
new(obj) string("1"); // string("1") is constructed
obj->~string (); // string("1") is destructed
new(obj) string("2"); // string("2") is constructed
obj->~string (); // string("2") is destructed
free(obj); // memory is deallocated
}
如果对象的生命周期超过了内存的生命周期,你的程序就会有 UB。确保内存 always 比对象的寿命长。例如,这有 UB:
void ub() {
alignas(string) char buf[sizeof(string)]; // memory is allocated
new(buf) string("1"); // string("1") is constructed
} // memory is deallocated but string("1") outlives the memory!
但这没关系:
void ub() {
alignas(string) char buf[sizeof(string)]; // memory is allocated
new(buf) string("1"); // string("1") is constructed
buf->~string(); // string("1") is destructed
} // memory is deallocated
请注意如何使用 alignas
正确对齐自动缓冲区。任意类型缺少 alignas
会导致 UB。它可能看起来有效,但这只会误导您。
有一些特定的类型,不调用析构函数和不正确对齐内存不会导致 UB,但你永远不应该对类型做出这样的假设。调用你的析构函数并进行对齐,如果它被证明是不必要的,它不会花费你任何东西 - 不会为这种类型生成额外的代码。
struct S {
char str[10];
}
How C++ placement new works?
...
I am trying to understand "placement new" and following is the program I came up with...
两个答案都很好。但是你也想知道它是如何工作的,所以,我将从汇编中添加解释:
A *obj1 = new A(21);
:
call operator new(unsigned long)
mov esi, 21
mov rdi, rax
mov rbx, rax
call A::A(int)
A *obj2 = new(obj1) A(22);
mov esi, 22
mov rdi, rbx
call A::A(int)
这就是它的工作原理,已经很清楚了,不需要再解释了吧?