簡體   English   中英

C++ 放置新功能如何工作?

[英]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 獲取相同的地址。 這只是巧合嗎?
  • 在第 15 行撥打 D'tor 是一個好習慣嗎?

還請指出您看到我可以改進的任何內容或只是不要嘗試。 也歡迎任何好的參考或閱讀。

這真的非常簡單: new可以被認為是做兩件事:

  1. 分配內存。
  2. 在分配的內存中放置構造對象。

不能保證malloc被實現實際使用,但通常是這樣。 你不能假設它的實現,但為了理解它是一個好的假設。

因此,以下內容被認為是等效的:

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);

然后,當您看到newdelete的真實情況時,您可以輕松地對這一切進行推理:分配后跟構造函數調用,以及析構函數調用后跟釋放。

當您使用placement new ,您已決定單獨處理第一步。 內存仍然必須以某種方式分配,您只需完全控制它的發生方式以及內存來自何處。

因此,您必須分別跟蹤兩件事:

  1. 記憶的壽命。

  2. 對象的生命周期。

下面的代碼演示了這些是如何相互獨立的:

#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。 確保內存始終比對象的生命周期長。 例如,這有 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];
}

這可能是CodeReview.SE 的內容,讓我在回答您的問題之前對您的源代碼進行評論。

A *obj1 = new A(21);
std::cout<<"Object allocated at "<<obj1<<std::endl;
obj1->~A();

您通常永遠不會在不是用placement-new 創建的對象上調用析構函數。 在你的情況下,你破壞了舊的並用placement-new 構建了一個新的。 即使這可行,您也應該實現一些重置功能來重置您的對象,而不是破壞和構建一個新對象。

17    obj1->testFunction();

這是 UB。 您已經破壞了該對象,您不應該對其調用任何方法。

18    A *obj2 = new(obj1) A(22);
19    obj1->testFunction();
20    obj2->testFunction();

很好,但請注意obj1obj2是完全相同的對象。

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,不要在解構或刪除的對象上調用方法。 對於您的問題:

成員 _a 是動態分配的(沒有新的位置)。 那么為什么它為 obj1 和 obj2 獲得相同的地址。 這只是巧合嗎?

不要稱它們為obj1obj2因為這兩個變量指向同一個對象,但是是的,這是巧合。 在第一個對象被破壞並釋放此內存后,第二個對象分配了剛剛釋放的相同數量的內存,分配器決定為您提供完全相同的內存。

在第 15 行撥打 D'tor 是一個好習慣嗎?

不,這不對。 為什么需要調用析構函數的示例很少,其中之一是您的對象是由placement-new 創建的。 在您的示例中,這沒有副作用,因為您在解構舊對象后在同一位置構造了一個新對象,並且新對象與舊對象具有相同的類型,否則這可能會以某種方式嚴重破壞。

現在更多關於您刪除后的評論。 讓我們看看new和placement-new 實際上做了什么。

一個新的:

  • 從操作系統為新對象分配內存
  • 在新對象上調用構造函數,地址( this )被設置為分配器獲得的內存塊。

刪除則相反:

  • 調用對象的析構函數
  • 釋放內存塊

現在到placement-new:placement-new 只是跳過第一步(分配內存)並調用對象的構造函數,並將this設置為您傳遞的地址。 因此,placement-new 的反面只是調用析構函數,因為不存在placement-delete。

這意味着對於您的代碼,在您調用析構函數之后,您的第一個對象死亡,但您從未將內存歸還,這就是您可以在該內存中構造一個新對象的原因。 現在當你調用 delete 時,第一個對象不再存在,只有它使用的內存,但是相同的內存現在被第二個對象阻塞,因此當你調用 delete 時,你不會刪除兩個對象,你只刪除第二個一個(你解構它,然后釋放內存塊)。

您可以在isocpp 的 faq 中閱讀有關主題放置新以及何時調用析構函數的更多信息

C++ 放置新功能如何工作?

...

我正在嘗試理解“placement new”,以下是我想出的程序……

這兩個答案都很棒。 但是您也想知道它是如何工作的,因此,我將添加程序集的解釋:


  • 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)

這就是它的工作原理,足夠清楚,不需要更多解釋,對吧?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM