C ++ 11 emplace_back บน vector <struct>?


89

พิจารณาโปรแกรมต่อไปนี้:

#include <string>
#include <vector>

using namespace std;

struct T
{
    int a;
    double b;
    string c;
};

vector<T> V;

int main()
{
    V.emplace_back(42, 3.14, "foo");
}

มันไม่ทำงาน:

$ g++ -std=gnu++11 ./test.cpp
In file included from /usr/include/c++/4.7/x86_64-linux-gnu/bits/c++allocator.h:34:0,
                 from /usr/include/c++/4.7/bits/allocator.h:48,
                 from /usr/include/c++/4.7/string:43,
                 from ./test.cpp:1:
/usr/include/c++/4.7/ext/new_allocator.h: In instantiation of ‘void __gnu_cxx::new_allocator<_Tp>::construct(_Up*, _Args&& ...) [with _Up = T; _Args = {int, double, const char (&)[4]}; _Tp = T]’:
/usr/include/c++/4.7/bits/alloc_traits.h:253:4:   required from ‘static typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type std::allocator_traits<_Alloc>::_S_construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = T; _Args = {int, double, const char (&)[4]}; _Alloc = std::allocator<T>; typename std::enable_if<std::allocator_traits<_Alloc>::__construct_helper<_Tp, _Args>::value, void>::type = void]’
/usr/include/c++/4.7/bits/alloc_traits.h:390:4:   required from ‘static void std::allocator_traits<_Alloc>::construct(_Alloc&, _Tp*, _Args&& ...) [with _Tp = T; _Args = {int, double, const char (&)[4]}; _Alloc = std::allocator<T>]’
/usr/include/c++/4.7/bits/vector.tcc:97:6:   required from ‘void std::vector<_Tp, _Alloc>::emplace_back(_Args&& ...) [with _Args = {int, double, const char (&)[4]}; _Tp = T; _Alloc = std::allocator<T>]’
./test.cpp:17:32:   required from here
/usr/include/c++/4.7/ext/new_allocator.h:110:4: error: no matching function for call to ‘T::T(int, double, const char [4])’
/usr/include/c++/4.7/ext/new_allocator.h:110:4: note: candidates are:
./test.cpp:6:8: note: T::T()
./test.cpp:6:8: note:   candidate expects 0 arguments, 3 provided
./test.cpp:6:8: note: T::T(const T&)
./test.cpp:6:8: note:   candidate expects 1 argument, 3 provided
./test.cpp:6:8: note: T::T(T&&)
./test.cpp:6:8: note:   candidate expects 1 argument, 3 provided

วิธีที่ถูกต้องในการทำเช่นนี้คืออะไรและทำไม?

(พยายามจัดฟันเดี่ยวและคู่ด้วย)


4
ซึ่งจะใช้ได้ผลหากคุณจัดหาตัวสร้างที่เหมาะสม
chris

3
มีวิธีสร้างแบบแทนที่ด้วยตัวสร้างโครงสร้างรั้งที่สร้างขึ้นโดยอัตโนมัติที่ใช้โดยT t{42,3.14, "foo"}หรือไม่?
Andrew Tomazos

4
ฉันไม่คิดว่าจะอยู่ในรูปแบบของตัวสร้าง เป็นการเริ่มต้นรวม
chris


5
ฉันไม่ได้พยายามที่จะส่งผลกระทบต่อความคิดเห็นของคุณ แต่อย่างใด .. แต่ในกรณีที่คุณไม่ได้ให้ความสนใจกับคำถามบาง ๆ มาระยะหนึ่ง .. คำตอบที่ได้รับการยอมรับด้วยความเคารพอย่างเต็มที่ต่อผู้เขียนไม่ใช่คำตอบสำหรับคำถามของคุณและ อาจทำให้ผู้อ่านเข้าใจผิด
Humam Helfawi

คำตอบ:


19

สำหรับทุกคนที่มาจากอนาคตลักษณะการทำงานนี้จะมีการเปลี่ยนแปลงในC ++ 20

กล่าวอีกนัยหนึ่งแม้ว่าการนำไปใช้งานภายในจะยังคงเรียกT(arg0, arg1, ...)ได้ว่าเป็นเรื่องปกติT{arg0, arg1, ...}ที่คุณคาดหวัง


97

คุณต้องกำหนด ctor อย่างชัดเจนสำหรับคลาส:

#include <string>
#include <vector>

using namespace std;

struct T
{
    int a;
    double b;
    string c;

    T(int a, double b, string &&c) 
        : a(a)
        , b(b)
        , c(std::move(c)) 
    {}
};

vector<T> V;

int main()
{
    V.emplace_back(42, 3.14, "foo");
}

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


12
ฉันคิดว่าวิธีที่ดีกว่าคือการเขียนT(int a, double b, string c) : a(a), b(b), c(std::move(c))
balki

9
emplace_backคำตอบที่ได้รับการยอมรับขัดแย้งกับวัตถุประสงค์ของ นี่คือคำตอบที่ถูกต้อง นี่คือวิธีการemplace*ทำงาน พวกเขาสร้างองค์ประกอบในสถานที่โดยใช้อาร์กิวเมนต์ที่ส่งต่อ ดังนั้นจึงจำเป็นต้องมีตัวสร้างเพื่อรับข้อโต้แย้งดังกล่าว
underscore_d

1
ยังเวกเตอร์สามารถให้ emplace_aggr ได้ใช่ไหม
tamas.kenez

@balki ถูกต้องไม่มีประเด็นใด ๆ ที่จะเกิดcขึ้น&&หากไม่มีอะไรเกิดขึ้นกับค่าความเที่ยงตรงที่เป็นไปได้ ที่ผู้เริ่มต้นของสมาชิกอาร์กิวเมนต์จะถือว่าเป็น lvalue อีกครั้งในกรณีที่ไม่มีนักแสดงดังนั้นสมาชิกจึงได้รับการสร้างสำเนา แม้ว่าสมาชิกจะถูกสร้างแบบย้าย แต่ก็ไม่ใช่สำนวนที่จะกำหนดให้ผู้โทรส่งผ่านค่าชั่วคราวหรือstd::move()d lvalue เสมอไป (แม้ว่าฉันจะยอมรับว่าฉันมีบางกรณีในรหัสของฉันที่ฉันทำ แต่ในรายละเอียดการใช้งานเท่านั้น) .
underscore_d

26

แน่นอนว่านี่ไม่ใช่คำตอบ แต่แสดงให้เห็นถึงคุณสมบัติที่น่าสนใจของสิ่งที่ดึงดูด:

#include <string>
#include <tuple>
#include <vector>

using namespace std;

using T = tuple <
    int,
    double,
    string
>;

vector<T> V;

int main()
{
    V.emplace_back(42, 3.14, "foo");
}

9
มันไม่ใช่คำตอบอย่างแน่นอน มีอะไรน่าสนใจบ้าง? ทุกประเภทที่มี ctor สามารถฝังได้ด้วยวิธีนี้ ทูเพิลมี ctor โครงสร้างของ op ไม่ได้ นั่นคือคำตอบ
underscore_d

6
@underscore_d: ฉันไม่แน่ใจว่าฉันจำทุกรายละเอียดของสิ่งที่ฉันคิดเมื่อ3½ปีที่แล้ว แต่ฉันบางสิ่งที่ฉันแนะนำคือถ้าคุณใช้ a tupleแทนการกำหนดโครงสร้าง POD คุณจะได้ตัวสร้างฟรี ซึ่งหมายความว่าคุณจะได้รับemplaceไวยากรณ์ฟรี (เหนือสิ่งอื่นใด - คุณจะได้รับคำสั่งพจนานุกรมด้วย) คุณเสียชื่อสมาชิก แต่บางครั้งการสร้าง accessors ก็น้อยกว่าส่วนที่เหลือทั้งหมดของต้นแบบที่คุณต้องการ ฉันยอมรับว่าคำตอบของ Jerry Coffin นั้นดีกว่าคำตอบที่ยอมรับมาก ฉันยังโหวตให้คะแนนเมื่อหลายปีก่อน
rici

3
ใช่การสะกดคำนั้นช่วยให้ฉันเข้าใจว่าคุณหมายถึงอะไร! จุดดี. ฉันยอมรับว่าบางครั้งการวางนัยทั่วไปก็สามารถรับได้เมื่อชั่งน้ำหนักเทียบกับสิ่งอื่น ๆ ที่ STL ให้ไว้สำหรับเรา: ฉันใช้แบบกึ่งบ่อยกับpair... แต่บางทีอาจtupleจะตามมาในอนาคต ขอบคุณสำหรับการขยาย!
underscore_d

12

หากคุณไม่ต้องการ (หรือไม่สามารถ) เพิ่มตัวสร้างได้ให้เชี่ยวชาญตัวจัดสรรสำหรับ T (หรือสร้างตัวจัดสรรของคุณเอง)

namespace std {
    template<>
    struct allocator<T> {
        typedef T value_type;
        value_type* allocate(size_t n) { return static_cast<value_type*>(::operator new(sizeof(value_type) * n)); }
        void deallocate(value_type* p, size_t n) { return ::operator delete(static_cast<void*>(p)); }
        template<class U, class... Args>
        void construct(U* p, Args&&... args) { ::new(static_cast<void*>(p)) U{ std::forward<Args>(args)... }; }
    };
}

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

void construct(T* p, int a, double b, const string& c) { ::new(static_cast<void*>(p)) T{ a, b, c }; }

ในฟังก์ชันการจัดสรรของคุณคุณไม่จำเป็นต้องกังวลเกี่ยวกับการจัดตำแหน่งหรือไม่? ดูstd::aligned_storage
Andrew Tomazos

ไม่มีปัญหา. ตามข้อกำหนดเอฟเฟกต์ของ "void * :: operator new (size_t size)" คือ "ฟังก์ชันการจัดสรรที่เรียกโดยนิพจน์ใหม่เพื่อจัดสรรขนาดไบต์ของหน่วยเก็บข้อมูลที่จัดแนวอย่างเหมาะสมเพื่อแสดงวัตถุใด ๆ ที่มีขนาดนั้น"
Mitsuru Kariya

6

สิ่งนี้ดูเหมือนจะครอบคลุมใน 23.2.1 / 13

ประการแรกคำจำกัดความ:

ระบุประเภทคอนเทนเนอร์ X ที่มีการจัดสรร _type เหมือนกับ A และ value_type เหมือนกับ T และกำหนดค่า lvalue m ประเภท A ตัวชี้ p ของประเภท T * นิพจน์ v ประเภท T และ rvalue rv ของประเภท T มีการกำหนดเงื่อนไขต่อไปนี้

ตอนนี้สิ่งที่ทำให้มันสร้างขึ้นเองได้:

T คือ EmplaceConstructible เป็น X จาก args สำหรับอาร์กิวเมนต์ที่เป็นศูนย์หรือมากกว่าหมายความว่านิพจน์ต่อไปนี้มีรูปแบบที่ดี: จัดสรร _traits :: สร้าง (m, p, args);

และในที่สุดก็เป็นข้อสังเกตเกี่ยวกับการใช้งานการเรียกสร้างเริ่มต้น

หมายเหตุ: คอนเทนเนอร์เรียกใช้การจัดสรร _traits :: สร้าง (m, p, args) เพื่อสร้างองค์ประกอบที่ p โดยใช้ args โครงสร้างเริ่มต้นใน std :: ตัวจัดสรรจะเรียก :: new ((void *) p) T (args) แต่ตัวจัดสรรแบบพิเศษอาจเลือกนิยามที่แตกต่างกัน

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


-2

คุณต้องกำหนดคอนสตรัคเตอร์สำหรับประเภทของคุณTเนื่องจากมีสิ่งstd::stringที่ไม่สำคัญ

ยิ่งไปกว่านั้นมันจะเป็นการดีกว่าที่จะกำหนด (เป็นไปได้ที่ผิดนัด) ย้าย ctor / กำหนด (เพราะคุณมีstd::stringสมาชิกที่สามารถเคลื่อนย้ายได้) ซึ่งจะช่วยให้คุณTมีประสิทธิภาพมากขึ้น ...

หรือใช้T{...}เพื่อโทรเกินemplace_back()ตามคำแนะนำในการตอบสนองของ Neighboug ... ทุกอย่างขึ้นอยู่กับกรณีการใช้งานทั่วไปของคุณ ...


ตัวสร้างการเคลื่อนไหวจะถูกสร้างขึ้นโดยอัตโนมัติสำหรับ T
Andrew Tomazos

1
@ AndrewTomazos-Fathomling: เฉพาะในกรณีที่ไม่มีการกำหนด ctors ผู้ใช้
zaufi

1
ถูกต้องและไม่เป็นเช่นนั้น
Andrew Tomazos

@ AndrewTomazos-Fathomling: แต่คุณต้องกำหนดบางอย่างเพื่อหลีกเลี่ยงอินสแตนซ์ชั่วคราวในการemplace_back()โทร :)
zaufi

1
ไม่ถูกต้องจริง ตัวสร้างการย้ายจะถูกสร้างขึ้นโดยอัตโนมัติหากไม่มีการกำหนดตัวทำลายตัวสร้างการคัดลอกหรือตัวดำเนินการกำหนด การกำหนดคอนสตรัคเตอร์แบบสมาชิก 3 อาร์กิวเมนต์สำหรับใช้กับ emplace_back จะไม่บีบตัวสร้างการย้ายเริ่มต้น
Andrew Tomazos

-2

คุณสามารถสร้างstruct Tอินสแตนซ์จากนั้นย้ายไปที่เวกเตอร์:

V.push_back(std::move(T {42, 3.14, "foo"}));

2
คุณไม่จำเป็นต้อง std :: move () วัตถุชั่วคราว T {... } มันเป็นวัตถุชั่วคราว (rvalue) อยู่แล้ว ดังนั้นคุณสามารถวาง std :: move () จากตัวอย่างของคุณได้
Nadav Har'El

ยิ่งไปกว่านั้นแม้แต่ชื่อประเภท T ก็ไม่จำเป็น - คอมไพเลอร์สามารถเดาได้ ดังนั้นเพียงแค่ "V.push_back {42, 3.14," foo "}" ก็ใช้ได้
Nadav Har'El

-8

คุณสามารถใช้{}ไวยากรณ์เพื่อเริ่มต้นองค์ประกอบใหม่:

V.emplace_back(T{42, 3.14, "foo"});

สิ่งนี้อาจปรับให้เหมาะสมหรือไม่ก็ได้ แต่ควรเป็นเช่นนั้น

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

T a(42, 3.14, "foo");

แต่นี่คือสิ่งที่คุณต้องมีในการทำงาน

ดังนั้น:

struct T { 
  ...
  T(int a_, double b_, string c_) a(a_), b(b_), c(c_) {}
}

จะทำให้มันทำงานได้ตามที่ต้องการ


10
สิ่งนี้จะสร้างชั่วคราวแล้วย้ายโครงสร้างไปยังอาร์เรย์หรือไม่? - หรือจะสร้างรายการขึ้นมาแทน?
Andrew Tomazos

3
std::moveไม่จำเป็น T{42, 3.14, "foo"}จะถูกส่งต่อโดย emplace_back และเชื่อมโยงกับคอนสตรัคการย้ายโครงสร้างเป็นค่า rvalue อย่างไรก็ตามฉันต้องการโซลูชันที่สร้างในสถานที่
Andrew Tomazos

37
ในกรณีนี้การย้ายเกือบจะเทียบเท่ากับการคัดลอกดังนั้นจึงพลาดจุดทั้งหมดของการเว้นวรรค
Alex I.

5
@ อเล็กซี. แน่นอน! ไวยากรณ์นี้สร้างชั่วคราวซึ่งส่งผ่านเป็นอาร์กิวเมนต์ไปยัง 'emplace_back' พลาดจุดนี้โดยสิ้นเชิง
aldo

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