Pertimbangkan program berikut:
#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");
}
Tidak berhasil:
$ 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
Apa cara yang benar untuk melakukan ini dan mengapa?
(Juga mencoba kawat gigi tunggal dan ganda)
T t{42,3.14, "foo"}
?Jawaban:
Untuk siapa pun di masa mendatang, perilaku ini akan diubah di C ++ 20 .
Dengan kata lain, meskipun implementasi secara internal akan tetap memanggil
T(arg0, arg1, ...)
itu akan dianggap biasa sepertiT{arg0, arg1, ...}
yang Anda harapkan.sumber
Anda perlu secara eksplisit mendefinisikan ctor untuk kelas:
#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"); }
Tujuan penggunaannya
emplace_back
adalah untuk menghindari pembuatan objek sementara, yang kemudian disalin (atau dipindahkan) ke tujuan. Meskipun dimungkinkan juga untuk membuat objek sementara, lalu meneruskannya keemplace_back
, itu mengalahkan (setidaknya sebagian besar) tujuannya. Apa yang ingin Anda lakukan adalah meneruskan argumen individual, lalu biarkanemplace_back
memanggil ctor dengan argumen tersebut untuk membuat objek pada tempatnya.sumber
T(int a, double b, string c) : a(a), b(b), c(std::move(c))
emplace_back
. Ini jawaban yang benar. Beginilah caraemplace*
kerjanya. Mereka membangun elemen di tempat menggunakan argumen yang diteruskan. Oleh karena itu, diperlukan seorang konstruktor untuk mengambil argumen tersebut.c
dengan&&
jika tidak dilakukan dengan rvalueness yang mungkin terjadi; pada penginisialisasi anggota, argumen diperlakukan sebagai nilai l lagi, jika tidak ada pemeran, sehingga anggota hanya mendapatkan salinan-konstruksi. Sekalipun anggota itu dibuat-pindah, bukanlah idiomatis untuk meminta penelepon selalu memberikan nilai sementara ataustd::move()
d l (meskipun saya akan mengakui bahwa saya memiliki beberapa kasus sudut dalam kode saya di mana saya melakukan itu, tetapi hanya dalam detail implementasi) .Tentu saja, ini bukan jawaban, tetapi ini menunjukkan fitur tupel yang menarik:
#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"); }
sumber
tuple
alih - alih mendefinisikan struct POD, maka Anda mendapatkan konstruktor secara gratis , yang berarti Anda mendapatkanemplace
sintaks secara gratis (antara lain - Anda juga mendapatkan pengurutan leksikografik). Anda kehilangan nama anggota, tapi terkadang tidak terlalu repot membuat pengakses daripada semua boilerplate lainnya yang seharusnya Anda butuhkan. Saya setuju bahwa jawaban Jerry Coffin jauh lebih baik daripada yang diterima. Saya juga memilihnya beberapa tahun lalu.pair
... tetapi kadang-kadang bertanya-tanya apakah saya benar-benar mendapatkan banyak keuntungan secara net, heh. Tapi mungkintuple
akan menyusul di masa depan. Terima kasih telah berkembang!Jika Anda tidak ingin (atau tidak dapat) menambahkan konstruktor, mengkhususkan pengalokasi untuk T (atau buat pengalokasi Anda sendiri).
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)... }; } }; }
Catatan: Konstruksi fungsi anggota yang ditunjukkan di atas tidak dapat dikompilasi dengan clang 3.1 (Maaf, saya tidak tahu mengapa). Coba yang berikutnya jika Anda akan menggunakan dentang 3.1 (atau alasan lain).
void construct(T* p, int a, double b, const string& c) { ::new(static_cast<void*>(p)) T{ a, b, c }; }
sumber
std::aligned_storage
Ini sepertinya tercakup dalam 23.2.1 / 13.
Pertama, definisi:
Sekarang, apa yang membuatnya bisa dibangun dengan emplace:
Dan terakhir catatan tentang implementasi default dari panggilan konstruksi:
Ini cukup banyak memberi tahu kita bahwa untuk skema pengalokasi default (dan kemungkinan satu-satunya) Anda harus telah mendefinisikan konstruktor dengan jumlah argumen yang tepat untuk hal yang Anda coba tempatkan-bangun ke dalam wadah.
sumber
Anda harus menentukan konstruktor untuk tipe Anda
T
karena mengandungstd::string
yang tidak sepele.Selain itu, akan lebih baik untuk mendefinisikan (kemungkinan default) pindah ctor / assign (karena Anda memiliki yang dapat dipindahkan
std::string
sebagai anggota) - ini akan membantu untuk memindahkan AndaT
jauh lebih efisien ...atau, cukup gunakan
T{...}
untuk panggilan yang kelebihan bebanemplace_back()
seperti yang direkomendasikan dalam respons tetangga ... semuanya tergantung pada kasus penggunaan khas Anda ...sumber
emplace_back()
panggilan :)Anda dapat membuat
struct T
instance dan kemudian memindahkannya ke vektor:V.push_back(std::move(T {42, 3.14, "foo"}));
sumber
Anda dapat menggunakan
{}
sintaks untuk menginisialisasi elemen baru:V.emplace_back(T{42, 3.14, "foo"});
Ini mungkin atau mungkin tidak dioptimalkan, tetapi seharusnya dioptimalkan.
Anda harus menentukan konstruktor agar ini berfungsi, perhatikan bahwa dengan kode Anda, Anda bahkan tidak dapat melakukan:
T a(42, 3.14, "foo");
Tapi inilah yang Anda butuhkan untuk memiliki pekerjaan emplace.
jadi hanya:
struct T { ... T(int a_, double b_, string c_) a(a_), b(b_), c(c_) {} }
akan membuatnya bekerja dengan cara yang diinginkan.
sumber
std::move
perlu.T{42, 3.14, "foo"}
sudah akan diteruskan oleh emplace_back dan mengikat ke struct move constructor sebagai rvalue. Namun saya lebih suka solusi yang membangunnya di tempat.