มีประโยชน์อะไรบ้างสำหรับ "การจัดวางใหม่"


410

มีใครที่นี่เคยใช้ "ตำแหน่งใหม่" ของ C ++ หรือไม่ ถ้าใช่มีไว้เพื่ออะไร ดูเหมือนว่าฉันจะเป็นประโยชน์กับฮาร์ดแวร์ที่แมปหน่วยความจำเท่านั้น


14
นี่เป็นเพียงข้อมูลที่ฉันค้นหาเพื่อเรียกใช้ตัวสร้างออบเจ็กต์ด้วยการเพิ่มพูลหน่วยความจำที่จัดสรร (หวังว่าคำหลักเหล่านี้จะทำให้คนอื่นค้นพบได้ง่ายขึ้นในอนาคต)
แทรกบ๊อบ

2
มันถูกใช้ในบทความ C ++ 11 Wikipediaในตัวสร้างของสหภาพ
HelloGoodbye

@HelloGoodbye น่าสนใจ! ในบทความที่คุณเชื่อมโยงทำไมไม่สามารถที่คุณทำp = ptและผู้ประกอบการใช้งานที่ได้รับมอบหมายของPointแทนการทำnew(&p) Point(pt)? ฉันสงสัยว่าความแตกต่างระหว่างคนทั้งสองนั้น จะโทรอดีตoperator=ในประเด็นขณะที่สายหลังคัดลอกสร้างของPoint? แต่ฉันก็ยังไม่ชัดเจนว่าทำไมคนหนึ่งถึงดีกว่าอีกคนหนึ่ง
Andrei-Niculae Petre

@ Andrei-NiculaePetre ฉันไม่ได้ใช้ตำแหน่งใหม่ด้วยตัวเอง แต่ฉันคิดว่าคุณควรใช้มันพร้อมกับตัวสร้างสำเนาถ้าคุณไม่มีออบเจ็กต์ของคลาสนั้นมิฉะนั้นคุณควรใช้โอเปอเรเตอร์การคัดลอก นอกเสียจากว่าชั้นจะเล็กน้อย แล้วมันไม่สำคัญว่าคุณจะใช้อันไหนของ สิ่งเดียวกันคือการทำลายของวัตถุ ความล้มเหลวในการจัดการกับสิ่งนี้อย่างถูกต้องสำหรับคลาสที่ไม่สำคัญอาจนำไปสู่พฤติกรรมที่แปลกและอาจทำให้เกิดพฤติกรรมที่ไม่ได้กำหนดในบางสถานการณ์
HelloGoodbye

@ Andrei-NiculaePetre ที่จริงแล้วฉันพบตัวอย่างในบทความ Wikipedia ค่อนข้างแย่เพราะมันแค่อนุมานว่าไม่มีวัตถุก่อนหน้านี้อยู่และพวกเขาจำเป็นต้องสร้างสิ่งหนึ่ง นี่ไม่ใช่กรณีถ้าU::operator=เพิ่งถูกเรียก
HelloGoodbye

คำตอบ:


364

ตำแหน่งใหม่ช่วยให้คุณสามารถสร้างวัตถุในหน่วยความจำที่จัดสรรไว้แล้ว

คุณอาจต้องการทำสิ่งนี้เพื่อปรับให้เหมาะสมเมื่อคุณต้องการสร้างวัตถุหลายอินสแตนซ์และเร็วกว่าที่จะไม่จัดสรรหน่วยความจำใหม่ทุกครั้งที่คุณต้องการอินสแตนซ์ใหม่ แต่อาจมีประสิทธิภาพมากกว่าในการจัดสรรครั้งเดียวสำหรับหน่วยความจำที่สามารถเก็บวัตถุหลายชิ้นได้แม้ว่าคุณจะไม่ต้องการใช้ทั้งหมดในครั้งเดียวก็ตาม

DevX เป็นตัวอย่างที่ดี :

มาตรฐาน C ++ ยังรองรับการวางตำแหน่งตัวดำเนินการใหม่ซึ่งสร้างวัตถุบนบัฟเฟอร์ที่จัดสรรไว้ล่วงหน้า สิ่งนี้มีประโยชน์เมื่อสร้างพูลหน่วยความจำตัวรวบรวมขยะหรือเพียงเมื่อประสิทธิภาพและข้อยกเว้นด้านความปลอดภัยเป็นสิ่งสำคัญที่สุด (ไม่มีอันตรายจากการจัดสรรล้มเหลวเนื่องจากหน่วยความจำได้รับการจัดสรรแล้วและการสร้างวัตถุบนบัฟเฟอร์ที่จัดสรรล่วงหน้าใช้เวลาน้อยลง) :

char *buf  = new char[sizeof(string)]; // pre-allocated buffer
string *p = new (buf) string("hi");    // placement new
string *q = new string("hi");          // ordinary heap allocation

คุณอาจต้องการให้แน่ใจว่าไม่มีความล้มเหลวในการจัดสรรในส่วนที่สำคัญของรหัสที่สำคัญ (ตัวอย่างเช่นในรหัสที่ดำเนินการโดยเครื่องกระตุ้นหัวใจ) ในกรณีที่คุณต้องการจัดสรรหน่วยความจำก่อนหน้านี้จากนั้นใช้ตำแหน่งใหม่ภายในส่วนที่สำคัญ

การจัดสรรคืนในตำแหน่งใหม่

คุณไม่ควรยกเลิกการจัดสรรทุกวัตถุที่ใช้บัฟเฟอร์หน่วยความจำ คุณควรลบ [] เฉพาะบัฟเฟอร์เดิมเท่านั้น คุณจะต้องเรียก destructors ของชั้นเรียนของคุณด้วยตนเอง สำหรับคำแนะนำที่ดีเกี่ยวกับสิ่งนี้โปรดดูคำถามที่พบบ่อยของ Stroustrup ใน: มี "การลบตำแหน่ง"หรือไม่


54
มันไม่ได้ถูกคัดค้านเนื่องจากคุณต้องการใช้คุณลักษณะนี้เพื่อนำวัตถุคอนเทนเนอร์ไปใช้อย่างมีประสิทธิภาพ (เช่นเวกเตอร์) หากคุณไม่ได้สร้างที่เก็บของคุณเองคุณไม่จำเป็นต้องใช้คุณสมบัตินี้
Martin York

26
นอกจากนี้ยังเป็นสิ่งสำคัญที่จะต้องจำไว้ว่า #include <memory> ไม่เช่นนั้นคุณอาจพบกับอาการปวดหัวที่น่ากลัวในบางแพลตฟอร์มที่ไม่รู้จักตำแหน่งใหม่โดยอัตโนมัติ
Ramon Zarazua B.

22
อย่างเคร่งครัดมันเป็นพฤติกรรมที่ไม่ได้กำหนดให้เรียกdelete[]ใช้charบัฟเฟอร์เดิม การใช้ตำแหน่งnewได้สิ้นสุดอายุการใช้งานของcharวัตถุต้นฉบับโดยการใช้ที่เก็บข้อมูลอีกครั้ง ถ้าตอนนี้คุณเรียกdelete[] bufชนิดไดนามิกของวัตถุที่ชี้ไปที่ไม่ตรงกับประเภทคงที่ของพวกเขาเพื่อให้คุณมีพฤติกรรมที่ไม่ได้กำหนด มันมีความสอดคล้องมากขึ้นเพื่อใช้operator new/ operator deleteจัดสรรหน่วยความจำดิบ inteded newสำหรับการใช้งานตามตำแหน่ง
CB Bailey

31
แน่นอนฉันจะข้ามไปใช้ heap ในเครื่องกระตุ้นหัวใจ :-)
Eli Bendersky

15
@RamonZarazua #include <new>ส่วนหัวผิดก็
bit2shift

63

เราใช้มันกับพูลหน่วยความจำที่กำหนดเอง เพียงแค่ภาพร่าง:

class Pool {
public:
    Pool() { /* implementation details irrelevant */ };
    virtual ~Pool() { /* ditto */ };

    virtual void *allocate(size_t);
    virtual void deallocate(void *);

    static Pool::misc_pool() { return misc_pool_p; /* global MiscPool for general use */ }
};

class ClusterPool : public Pool { /* ... */ };
class FastPool : public Pool { /* ... */ };
class MapPool : public Pool { /* ... */ };
class MiscPool : public Pool { /* ... */ };

// elsewhere...

void *pnew_new(size_t size)
{
   return Pool::misc_pool()->allocate(size);
}

void *pnew_new(size_t size, Pool *pool_p)
{
   if (!pool_p) {
      return Pool::misc_pool()->allocate(size);
   }
   else {
      return pool_p->allocate(size);
   }
}

void pnew_delete(void *p)
{
   Pool *hp = Pool::find_pool(p);
   // note: if p == 0, then Pool::find_pool(p) will return 0.
   if (hp) {
      hp->deallocate(p);
   }
}

// elsewhere...

class Obj {
public:
   // misc ctors, dtors, etc.

   // just a sampling of new/del operators
   void *operator new(size_t s)             { return pnew_new(s); }
   void *operator new(size_t s, Pool *hp)   { return pnew_new(s, hp); }
   void operator delete(void *dp)           { pnew_delete(dp); }
   void operator delete(void *dp, Pool*)    { pnew_delete(dp); }

   void *operator new[](size_t s)           { return pnew_new(s); }
   void *operator new[](size_t s, Pool* hp) { return pnew_new(s, hp); }
   void operator delete[](void *dp)         { pnew_delete(dp); }
   void operator delete[](void *dp, Pool*)  { pnew_delete(dp); }
};

// elsewhere...

ClusterPool *cp = new ClusterPool(arg1, arg2, ...);

Obj *new_obj = new (cp) Obj(arg_a, arg_b, ...);

ตอนนี้คุณสามารถจัดกลุ่มวัตถุเข้าด้วยกันในเวทีหน่วยความจำเดี่ยวเลือกตัวจัดสรรที่รวดเร็วมาก แต่ไม่มีการจัดสรรคืนใช้การแมปหน่วยความจำและความหมายอื่น ๆ ที่คุณต้องการกำหนดโดยเลือกพูลและส่งผ่านเป็นอาร์กิวเมนต์ไปยังตำแหน่งของวัตถุ ผู้ประกอบการใหม่


1
อ๋อ เราค่อนข้างฉลาดเกี่ยวกับเรื่องนี้ แต่มันไม่ใช่หัวข้อสำหรับคำถามนี้
Don Wakefield

2
@jdkoftinoff คุณมีลิงค์ไปยังตัวอย่างโค้ดจริงหรือไม่? ดูเหมือนว่าน่าสนใจสำหรับฉัน!
Victor

@DonWakefield คุณจัดการกับการจัดแนวในกลุ่มนี้ได้อย่างไร คุณไม่ควรส่งการจัดตำแหน่งเป็นอาร์กิวเมนต์ไปยังที่allocate()อื่นหรือ
มิคาอิล Vasilyev

1
@MikailVasilyev ในการดำเนินการจริงคุณจะจัดการกับมันแน่นอน ตัวอย่างรหัสเท่านั้น
Don Wakefield

ถ้าตำแหน่งนั้นเป็นที่อยู่ที่ไม่ถูกต้องให้พูดว่า 0x0
Charlie

51

มีประโยชน์ถ้าคุณต้องการแยกการจัดสรรจากการเริ่มต้น STL ใช้ตำแหน่งใหม่เพื่อสร้างองค์ประกอบคอนเทนเนอร์


35

ฉันใช้มันในการเขียนโปรแกรมแบบเรียลไทม์ โดยทั่วไปเราไม่ต้องการทำการจัดสรรแบบไดนามิกใด ๆ (หรือยกเลิกการจัดสรรคืน) หลังจากที่ระบบเริ่มทำงานเนื่องจากไม่มีการรับประกันว่าจะใช้เวลานานเท่าใด

สิ่งที่ฉันสามารถทำได้คือ preallocate หน่วยความจำขนาดใหญ่ (มีขนาดใหญ่พอที่จะเก็บสิ่งที่ชั้นเรียนต้องการได้) จากนั้นเมื่อฉันคิดหาวิธีการสร้างสิ่งต่าง ๆ ที่รันไทม์ตำแหน่งใหม่สามารถใช้เพื่อสร้างวัตถุได้ทันทีที่ฉันต้องการ สถานการณ์หนึ่งที่ฉันรู้ว่าฉันใช้มันในการที่จะช่วยในการสร้างที่แตกต่างกันชนกลม

ไม่แน่นอนสำหรับคนที่ใจอ่อน แต่นั่นเป็นเหตุผลว่าทำไมพวกเขาจึงสร้างมันขึ้นมาสำหรับมัน


สวัสดี TED คุณช่วยแบ่งปันเพิ่มเติมเกี่ยวกับวิธีแก้ปัญหาที่คุณมีได้ไหม ฉันกำลังคิดหาวิธีแก้ปัญหาที่จัดสรรไว้ล่วงหน้า แต่ยังไม่มีความคืบหน้ามากนัก ขอบคุณล่วงหน้า!
Viet

1
รหัสบัฟเฟอร์วงกลม hetrogenious ที่เกิดขึ้นจริงนั้นเป็นส่วนที่ยากมากที่จะทำให้ถูกต้อง รูปลักษณ์ใหม่ดูน่ากลัวเล็กน้อย แต่จากการเปรียบเทียบมันก็ไม่มีปัญหาอะไรเลย
TED

26

ฉันใช้มันเพื่อสร้างวัตถุที่จัดสรรใน stack ผ่าน alloca ()

เสียบด้านหน้า:ฉัน blogged เกี่ยวกับเรื่องนี้ที่นี่


บทความที่น่าสนใจ boost::arrayแต่ผมไม่แน่ใจว่าผมเข้าใจประโยชน์ของการใช้มากกว่านี้ คุณสามารถขยายที่เล็กน้อย?
GrahamS

boost :: array ต้องการขนาดของอาร์เรย์เพื่อให้เป็นค่าคงที่เวลารวบรวม สิ่งนี้ไม่มีข้อ จำกัด
Ferruccio

2
@Ferruccio อันนี้ค่อนข้างเจ๋งฉันสังเกตว่ามาโครของคุณไม่ปลอดภัยเล็กน้อยแม้ว่าขนาดจะเป็นแบบ exepression หาก x + 1 ถูกส่งผ่านไปเช่นคุณจะขยายเป็น sizeof (type) * x + 1 ซึ่งจะไม่ถูกต้อง คุณต้องวงเล็บมาโครเพื่อให้ปลอดภัยยิ่งขึ้น
Benjamin

การใช้งานกับ alloca นั้นดูเป็นอันตรายต่อฉันถ้ามีการโยนข้อยกเว้นเนื่องจากคุณต้องโทรหาผู้ทำลายล้างบนวัตถุทั้งหมดของคุณ
CashCow

14

หัวหน้า Geek: BINGO! คุณได้รับทั้งหมด - นั่นคือสิ่งที่สมบูรณ์แบบ ในสภาพแวดล้อมแบบฝังตัวจำนวนมากข้อ จำกัด ภายนอกและ / หรือสถานการณ์การใช้โดยรวมบังคับให้โปรแกรมเมอร์แยกการจัดสรรวัตถุออกจากการเริ่มต้น รวมเข้าด้วยกัน C ++ เรียก "instantiation" นี้ แต่เมื่อใดก็ตามที่การกระทำของตัวสร้างจะต้องเรียกอย่างชัดเจนโดยไม่ต้องจัดสรรแบบไดนามิกหรืออัตโนมัติตำแหน่งใหม่เป็นวิธีที่จะทำ นอกจากนี้ยังเป็นวิธีที่สมบูรณ์แบบในการค้นหาวัตถุ C ++ ทั่วโลกที่ตรึงไว้กับที่อยู่ของส่วนประกอบฮาร์ดแวร์ (หน่วยความจำที่แมป I / O) หรือวัตถุคงที่ที่ด้วยเหตุผลใดก็ตามจะต้องอยู่ในที่อยู่ถาวร


12

ฉันเคยใช้มันเพื่อสร้างคลาส Variant (เช่นวัตถุที่สามารถแทนค่าเดียวที่สามารถเป็นหนึ่งในประเภทที่แตกต่างกันจำนวนหนึ่ง)

หากประเภทค่าทั้งหมดที่สนับสนุนโดยคลาส Variant เป็นชนิด POD (เช่น int, float, double, bool) ดังนั้นการรวมสหภาพสไตล์ C ที่ติดแท็กก็เพียงพอแล้ว แต่ถ้าคุณต้องการให้ประเภทค่าบางอย่างเป็นวัตถุ C ++ ( เช่น std :: string) คุณลักษณะ C union จะไม่ทำเนื่องจากประเภทข้อมูลที่ไม่ใช่ POD อาจไม่ถูกประกาศเป็นส่วนหนึ่งของสหภาพ

ดังนั้นฉันจึงจัดสรรอาร์เรย์ไบต์ที่มีขนาดใหญ่พอ (เช่น sizeof (the_largest_data_type_I_support)) และใช้การวางตำแหน่งใหม่เพื่อเริ่มต้นวัตถุ C ++ ที่เหมาะสมในพื้นที่นั้นเมื่อตัวแปรถูกตั้งค่าให้เป็นประเภทนั้น (และลบตำแหน่งไว้ล่วงหน้าเมื่อเปลี่ยนจากประเภทข้อมูลที่ไม่ใช่ POD อื่น)


คุณสามารถประกาศประเภทข้อมูลที่ไม่ใช่ POD ภายในสหภาพได้ตราบใดที่คุณให้สหภาพ ctor - และเฮ้ - ctor นั้นอาจใช้ตำแหน่งnewเพื่อเริ่มต้น subclass ที่ไม่ใช่ POD Ref: stackoverflow.com/a/33289972/2757035การสร้างวงล้อใหม่โดยใช้อาร์เรย์ไบต์ที่มีขนาดใหญ่ตามใจชอบคือการแสดงผาดโผนที่น่าประทับใจ แต่ดูเหมือนไม่จำเป็นทั้งหมดฉันพลาดอะไรไป? :)
underscore_d

6
คุณพลาดรุ่น C ++ ทั้งหมดก่อน C ++ 11 ซึ่งในหลายกรณียังต้องได้รับการสนับสนุน :)
Jeremy Friesner

10

ตำแหน่งใหม่ยังมีประโยชน์อย่างมากเมื่อทำให้เป็นอนุกรม (พูดด้วย boost :: serialization) ใน 10 ปีของ c ++ นี่เป็นเพียงกรณีที่สองที่ฉันต้องการตำแหน่งใหม่สำหรับ (ที่สามถ้าคุณรวมถึงการสัมภาษณ์ :))


9

นอกจากนี้ยังมีประโยชน์เมื่อคุณต้องการเริ่มต้นโครงสร้างโกลบอลหรือการจัดสรรแบบคงที่อีกครั้ง

ใช้วิธี C แบบเก่าmemset()เพื่อตั้งค่าองค์ประกอบทั้งหมดเป็น 0 คุณไม่สามารถทำได้ใน C ++ เนื่องจาก vtables และตัวสร้างวัตถุที่กำหนดเอง

ดังนั้นบางครั้งฉันก็ใช้สิ่งต่อไปนี้

 static Mystruct m;

 for(...)  {
     // re-initialize the structure. Note the use of placement new
     // and the extra parenthesis after Mystruct to force initialization.
     new (&m) Mystruct();

     // do-some work that modifies m's content.
 }

1
คุณไม่จำเป็นต้องทำการทำลายที่สอดคล้องกันก่อนที่จะเริ่มต้นอีกครั้งด้วยวิธีนั้น
Head Geek

[แก้ไขเพื่อการสะกดคำ] โดยปกติ - คุณทำ แต่บางครั้งเมื่อคุณรู้ว่าคลาสไม่ได้จัดสรรหน่วยความจำหรือทรัพยากรอื่น ๆ (หรือคุณจัดสรรคืนจากภายนอก - ตัวอย่างเช่นเมื่อคุณใช้พูลหน่วยความจำ) คุณสามารถใช้เทคนิคนี้ มันรับประกันได้ว่าตัวชี้ v-table ไม่ถูกเขียนทับ - nimrodm 16 ชั่วโมงก่อน
nimrodm

1
แม้แต่ใน C การใช้การตั้งค่าบิตทั้งหมดเป็น 0 รับประกันได้เพียงเพื่อสร้างการแทนค่า 0 สำหรับประเภทที่สมบูรณ์ไม่ใช่ประเภทอื่น ๆ (ตัวชี้โมฆะอาจมีการแสดงที่ไม่ใช่ศูนย์)
curiousguy

@currguy - สำหรับประเภทดั้งเดิมคุณถูกต้อง (มันจะทำให้โปรแกรมสามารถคาดการณ์ได้ซึ่งเป็นข้อได้เปรียบเมื่อมันมาถึงการดีบัก) อย่างไรก็ตามประเภทข้อมูล C ++ จะมีการเรียกใช้คอนสตรัคเตอร์ของตัวเอง (แทนที่) และจะเริ่มต้นได้อย่างถูกต้อง
nimrodm

9

จริง ๆ แล้วเป็นชนิดที่จำเป็นในการใช้โครงสร้างข้อมูลชนิดใด ๆ ที่จัดสรรหน่วยความจำได้มากกว่าที่จำเป็นเล็กน้อยสำหรับจำนวนองค์ประกอบที่ใส่เข้าไป (เช่นสิ่งอื่นใดนอกจากโครงสร้างที่เชื่อมโยงซึ่งจัดสรรหนึ่งโหนดต่อครั้ง)

ภาชนะที่ใช้เวลาชอบunordered_map, หรือvector dequeสิ่งเหล่านี้ทั้งหมดจัดสรรหน่วยความจำมากกว่าที่จำเป็นสำหรับองค์ประกอบที่คุณใส่น้อยที่สุดเพื่อหลีกเลี่ยงการจัดสรรฮีปสำหรับการแทรกทุกครั้ง ลองใช้vectorเป็นตัวอย่างที่ง่ายที่สุด

เมื่อคุณทำ:

vector<Foo> vec;

// Allocate memory for a thousand Foos:
vec.reserve(1000);

... ที่ไม่ได้สร้างพัน Foos จริงๆ มันเพียงจัดสรร / จองหน่วยความจำสำหรับพวกเขา หากvectorไม่ได้ใช้ตำแหน่งใหม่ที่นี่จะเป็นการสร้างค่าเริ่มต้นFoosไปทั่วสถานที่รวมถึงการเรียกใช้ destructors ของพวกเขาแม้สำหรับองค์ประกอบที่คุณไม่เคยใส่แม้แต่ในสถานที่แรก

การจัดสรร! = การก่อสร้างการปลดปล่อย! = การทำลาย

เพียงแค่พูดโดยทั่วไปเพื่อใช้โครงสร้างข้อมูลจำนวนมากเช่นด้านบนคุณไม่สามารถทำการจัดสรรหน่วยความจำและสร้างองค์ประกอบเป็นสิ่งที่แบ่งแยกไม่ได้และคุณก็ไม่สามารถรักษาหน่วยความจำที่ว่างเปล่าและทำลายองค์ประกอบได้เช่นกัน

จะต้องมีการแยกระหว่างความคิดเหล่านี้เพื่อหลีกเลี่ยงการอ้างถึง constructors และ destructors ที่ไม่จำเป็นซ้ายและขวาโดยไม่จำเป็นและนั่นเป็นสาเหตุที่ไลบรารี่มาตรฐานแยกความคิดของstd::allocator(ซึ่งไม่ได้สร้างหรือทำลายองค์ประกอบเมื่อจัดสรร / ปลดปล่อยหน่วยความจำ *) ภาชนะบรรจุที่ใช้มันซึ่งสร้างองค์ประกอบด้วยตนเองโดยใช้การจัดวางองค์ประกอบใหม่และทำลายองค์ประกอบด้วยตนเองโดยใช้การเรียกใช้อย่างชัดเจนของ destructors

  • ฉันเกลียดการออกแบบstd::allocatorแต่นั่นเป็นอีกเรื่องที่ฉันจะหลีกเลี่ยงการพูดจาโผงผาง :-D

ดังนั้นฉันมักจะใช้มันมากตั้งแต่ฉันเขียนคอนเทนเนอร์ C ++ ที่เป็นไปตามมาตรฐานทั่วไปที่ไม่สามารถสร้างในแง่ของคอนเทนเนอร์ที่มีอยู่ทั่วไปได้ สิ่งที่รวมอยู่ในนั้นคือการนำเวกเตอร์ขนาดเล็กมาใช้ฉันได้สร้างสองสามทศวรรษที่ผ่านมาเพื่อหลีกเลี่ยงการจัดสรรฮีปในกรณีทั่วไปและ trie ที่มีประสิทธิภาพหน่วยความจำ (ไม่จัดสรรทีละโหนด) ในทั้งสองกรณีฉันไม่สามารถนำไปใช้งานจริงได้โดยใช้คอนเทนเนอร์ที่มีอยู่ดังนั้นฉันจึงต้องใช้placement newเพื่อหลีกเลี่ยงการกล่าวอ้างสิ่งก่อสร้างและสิ่งทำลายล้างในสิ่งที่ไม่จำเป็นทั้งซ้ายและขวา

โดยปกติถ้าคุณทำงานร่วมกับตัวจัดสรรแบบกำหนดเองเพื่อจัดสรรวัตถุเป็นรายบุคคลเช่นรายการฟรีคุณก็มักต้องการใช้placement newเช่นนี้ (ตัวอย่างพื้นฐานที่ไม่รบกวนความปลอดภัยหรือ RAII):

Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);

8

มันจะมีประโยชน์ถ้าคุณกำลังสร้างเคอร์เนล - คุณวางรหัสเคอร์เนลที่คุณอ่านจากดิสก์หรือ pagetable ที่ไหน? คุณจำเป็นต้องรู้ว่าจะข้ามไปที่ใด

หรือในสถานการณ์อื่นที่หายากมากเช่นเมื่อคุณมีห้องที่จัดสรรไว้จำนวนมากและต้องการวางโครงสร้างสองสามหลัง สามารถบรรจุได้ด้วยวิธีนี้โดยไม่จำเป็นต้องใช้ตัวดำเนินการ offsetof () มีเทคนิคอื่น ๆ สำหรับสิ่งนั้นด้วยเช่นกัน

ฉันยังเชื่อว่าการใช้งาน STL บางอย่างทำให้การใช้งานของตำแหน่งใหม่เช่น std :: vector พวกเขาจัดสรรห้องสำหรับองค์ประกอบ 2 ^ n ด้วยวิธีนั้นและไม่จำเป็นต้องจัดสรรใหม่เสมอ


การลดการจัดสรรหน่วยความจำเป็นเหตุผลหลักอย่างหนึ่งที่ใช้งานเช่นเดียวกับ "กลอุบาย" เช่นการโหลดวัตถุออกจากดิสก์
lefticus

ฉันไม่รู้เมล็ดที่เขียนด้วยภาษา C ++; เมล็ดส่วนใหญ่เขียนเป็นเส้นตรง C.
Adam Rosenfield

8
ระบบปฏิบัติการที่ฉันได้เรียนรู้พื้นฐานของระบบปฏิบัติการนั้นเขียนใน C ++: sweb.sourceforge.net
mstrobl

8

ฉันคิดว่านี่ไม่ได้เน้นคำตอบใด ๆ แต่อีกตัวอย่างที่ดีและการใช้งานสำหรับตำแหน่งใหม่คือการลดการกระจายตัวของหน่วยความจำ (โดยใช้พูลหน่วยความจำ) สิ่งนี้มีประโยชน์เป็นพิเศษในระบบฝังตัวและความพร้อมใช้งานสูง ในกรณีสุดท้ายนี้มันมีความสำคัญเป็นพิเศษเพราะสำหรับระบบที่ต้องทำงาน 24/365 วันมันสำคัญมากที่จะไม่มีการแยกส่วน ปัญหานี้ไม่เกี่ยวข้องกับการรั่วไหลของหน่วยความจำ

แม้ว่าจะใช้งาน malloc ที่ดีมาก (หรือฟังก์ชั่นการจัดการหน่วยความจำที่คล้ายกัน) มันก็ยากที่จะจัดการกับการแตกแฟรกเมนต์เป็นเวลานาน ในบางจุดหากคุณไม่จัดการการจองหน่วยความจำ / การโทรอย่างชาญฉลาดคุณอาจท้ายด้วยช่องว่างเล็ก ๆจำนวนมากที่ยากต่อการใช้ซ้ำ (กำหนดให้กับการจองใหม่) ดังนั้นหนึ่งในโซลูชันที่ใช้ในกรณีนี้คือใช้พูลหน่วยความจำเพื่อจัดสรรก่อนส่งมอบหน่วยความจำสำหรับแอปพลิเคชันวัตถุ After-wards ในแต่ละครั้งที่คุณต้องการหน่วยความจำสำหรับวัตถุบางอย่างคุณเพียงแค่ใช้ตำแหน่งใหม่เพื่อสร้างวัตถุใหม่ในหน่วยความจำที่สงวนไว้แล้ว

วิธีนี้เมื่อแอปพลิเคชันของคุณเริ่มต้นคุณมีหน่วยความจำที่สงวนไว้ทั้งหมดแล้ว การจอง / รีลีสหน่วยความจำใหม่ทั้งหมดไปที่พูลที่จัดสรร (คุณอาจมีหลายพูลหนึ่งอันสำหรับคลาสอ็อบเจ็กต์ที่แตกต่างกัน) ไม่มีการแตกแฟรกเมนต์หน่วยความจำเกิดขึ้นในกรณีนี้เนื่องจากจะไม่มีช่องว่างและระบบของคุณสามารถทำงานเป็นระยะเวลานาน (ปี) โดยไม่ต้องทนทุกข์จากการแตกแฟรกเมนต์

ฉันเห็นสิ่งนี้ในทางปฏิบัติเป็นพิเศษสำหรับ VxWorks RTOS เนื่องจากระบบการจัดสรรหน่วยความจำเริ่มต้นได้รับความทุกข์ทรมานจากการแตกแฟรกเมนต์จำนวนมาก ดังนั้นการจัดสรรหน่วยความจำด้วยวิธีใหม่ / malloc มาตรฐานเป็นสิ่งต้องห้ามโดยทั่วไปในโครงการ การจองหน่วยความจำทั้งหมดควรไปที่พูลหน่วยความจำเฉพาะ


8

โดยจะใช้โดยstd::vector<>เพราะstd::vector<>มักจะจัดสรรหน่วยความจำมากขึ้นกว่าที่มีในobjectsvector<>


7

ฉันใช้มันเพื่อจัดเก็บวัตถุด้วยไฟล์ที่แมปหน่วยความจำ
ตัวอย่างที่เฉพาะเจาะจงคือฐานข้อมูลภาพซึ่งประมวลผลภาพขนาดใหญ่จำนวนมาก (สามารถใส่ในหน่วยความจำได้มากกว่า)


7

ฉันเคยเห็นว่ามันใช้เป็นแฮ็คประสิทธิภาพเล็กน้อยสำหรับตัวชี้ "ไดนามิกประเภท" (ในส่วน "Under the Hood"):

แต่นี่คือเคล็ดลับที่ยุ่งยากที่ฉันเคยได้รับเพื่อประสิทธิภาพการทำงานที่รวดเร็วสำหรับประเภทเล็ก ๆ : ถ้าค่าที่เก็บไว้สามารถพอดีกับภายในโมฆะ * ฉันไม่ได้สนใจการจัดสรรวัตถุใหม่จริง ๆ ฉันบังคับให้มันเข้าสู่ตัวชี้โดยใช้ตำแหน่งใหม่ .


จะเกิดอะไรขึ้นถ้าค่าที่ถูกเก็บไว้สามารถอยู่ภายในช่องว่างภายใน *หมายถึงอะไร เป็นไปได้เสมอที่จะกำหนดประเภทพอยน์เตอร์ให้เป็นโมฆะ * คุณช่วยยกตัวอย่างให้เราหน่อยได้ไหม
anurag86

@ anurag86: บนเครื่อง 64 บิตของฉัน a void*ใช้เวลา 8 ไบต์ มันเป็นเรื่องเล็ก ๆ น้อย ๆ โง่ที่จะชี้แปดไบต์ที่ไบต์หนึ่งvoid* boolแต่มันเป็นไปได้ทั้งหมดที่จะซ้อนทับboolบนจริง ๆvoid*เหมือนunion { bool b; void* v }กัน คุณต้องการวิธีที่จะรู้ว่าสิ่งที่คุณเรียกว่าvoid*เป็นจริงbool(หรือshortหรือหรือfloatฯลฯ ) บทความที่ฉันเชื่อมโยงเพื่ออธิบายวิธีการทำ และเพื่อตอบคำถามเดิมการจัดวางnewเป็นคุณสมบัติที่ใช้ในการสร้างbool(หรือประเภทอื่น ๆ ) ที่void*คาดว่าจะใช้ (ปลดเปลื้องจะได้รับ / แก้ไขค่าในภายหลัง)
Max Lybbert

@ anurag86: มันไม่เหมือนเดิม แต่คุณอาจสนใจพอยน์เตอร์ที่ติดแท็ก ( en.wikipedia.org/wiki/Tagged_pointer )
Max Lybbert

6

ฉันใช้มันเพื่อสร้างวัตถุตามหน่วยความจำที่มีข้อความที่ได้รับจากเครือข่าย


5

โดยทั่วไปตำแหน่งใหม่จะใช้เพื่อกำจัดค่าใช้จ่ายในการจัดสรรของ 'ปกติใหม่'

อีกสถานการณ์หนึ่งที่ฉันใช้มันเป็นสถานที่ที่ฉันต้องการเข้าถึงตัวชี้ไปยังวัตถุที่ยังคงถูกสร้างขึ้นเพื่อนำไปใช้กับเอกสารเดี่ยว



4

ที่เดียวที่ฉันเจอมันอยู่ในคอนเทนเนอร์ที่จัดสรรบัฟเฟอร์ที่ต่อเนื่องกันแล้วเติมด้วยวัตถุตามต้องการ ตามที่กล่าวไว้ std :: vector อาจทำเช่นนี้และฉันรู้ว่าบางรุ่นของ MFC CArray และ / หรือ CList ทำสิ่งนี้ (เพราะนั่นคือสิ่งที่ฉันต้องเจอก่อน) วิธีการจัดสรรบัฟเฟอร์มากเกินไปเป็นการเพิ่มประสิทธิภาพที่มีประโยชน์มากและการจัดวางตำแหน่งใหม่เป็นวิธีเดียวที่จะสร้างวัตถุในสถานการณ์นั้น บางครั้งมันก็ใช้เพื่อสร้างวัตถุในบล็อกหน่วยความจำที่จัดสรรนอกรหัสโดยตรงของคุณ

ฉันใช้มันในความสามารถที่คล้ายกันถึงแม้ว่ามันจะไม่เกิดขึ้นบ่อย มันเป็นเครื่องมือที่มีประโยชน์สำหรับกล่องเครื่องมือ C ++


4

เอ็นจินสคริปต์สามารถใช้ในอินเทอร์เฟซดั้งเดิมเพื่อปันส่วนวัตถุดั้งเดิมจากสคริปต์ ดู Angelscript (www.angelcode.com/angelscript) สำหรับตัวอย่าง


3

ดูไฟล์ fp.h ในโปรเจ็กต์ xll ที่http://xll.codeplex.comมันแก้ปัญหา "ความไม่มีเหตุผลกับคอมไพเลอร์" สำหรับอาร์เรย์ที่ต้องการนำมิติรอบตัวไปด้วย

typedef struct _FP
{
    unsigned short int rows;
    unsigned short int columns;
    double array[1];        /* Actually, array[rows][columns] */
} FP;

2

นี่คือ killer ใช้สำหรับคอนสตรัคเตอร์ C ++ แบบ In-place: การจัดแนวกับแคชบรรทัดรวมทั้งพลังอื่น ๆ ของ 2 ขอบเขต นี่คืออัลกอริทึมการจัดตำแหน่งตัวชี้ที่รวดเร็วเป็นพิเศษสำหรับกำลังของ 2 ขอบเขตพร้อมคำแนะนำรอบเดียว 5 หรือน้อยกว่า :

/* Quickly aligns the given pointer to a power of two boundary IN BYTES.
@return An aligned pointer of typename T.
@brief Algorithm is a 2's compliment trick that works by masking off
the desired number in 2's compliment and adding them to the
pointer.
@param pointer The pointer to align.
@param boundary_byte_count The boundary byte count that must be an even
power of 2.
@warning Function does not check if the boundary is a power of 2! */
template <typename T = char>
inline T* AlignUp(void* pointer, uintptr_t boundary_byte_count) {
  uintptr_t value = reinterpret_cast<uintptr_t>(pointer);
  value += (((~value) + 1) & (boundary_byte_count - 1));
  return reinterpret_cast<T*>(value);
}

struct Foo { Foo () {} };
char buffer[sizeof (Foo) + 64];
Foo* foo = new (AlignUp<Foo> (buffer, 64)) Foo ();

ตอนนี้ไม่เพียงแค่ทำให้รอยยิ้มบนใบหน้าของคุณ (:-) ฉัน♥♥♥ C ++ 1x

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.