C ++ 11 emplace_back vektörü <yapı>?


89

Aşağıdaki programı düşünün:

#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");
}

Çalışmıyor:

$ 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

Bunu yapmanın doğru yolu nedir ve neden?

(Ayrıca tek ve çift diş telleri denendi)


4
Uygun bir kurucu sağlarsanız bu işe yarayacaktır.
chris

3
Tarafından kullanılan otomatik olarak oluşturulan brace struct yapıcısı ile yerinde oluşturmanın bir yolu var mı T t{42,3.14, "foo"}?
Andrew Tomazos

4
Bunun bir kurucu şeklinde olduğunu sanmıyorum. Toplu başlatma.
chris


5
Fikrinizi hiçbir şekilde etkilemeye çalışmıyorum .. Ama bir süredir ince bir soruya dikkat etmediyseniz .. Yazarına tam saygıyla kabul edilen cevap, sorunuzun cevabı değildir ve okuyucuları yanıltabilir.
Humam Helfawi

Yanıtlar:


19

Gelecekten olanlar için, bu davranış değişecektir içinde C ++ 20 .

Başka bir deyişle, dahili olarak uygulama yine de çağıracak T(arg0, arg1, ...)olsa da T{arg0, arg1, ...}, beklediğiniz normal olarak kabul edilecektir .


97

Sınıf için açıkça bir ctor tanımlamanız gerekir:

#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");
}

Kullanım emplace_backamacı, daha sonra hedefe kopyalanan (veya taşınan) geçici bir nesne oluşturmaktan kaçınmaktır. Geçici bir nesne yaratmak ve sonra emplace_backonu geçmek de mümkün olsa da, amacı (en azından çoğunu) bozar. Yapmak istediğiniz şey, bağımsız argümanlar iletmek, ardından emplace_backnesneyi yerinde oluşturmak için ctor'u bu argümanlarla çağırmaktır.


12
T(int a, double b, string c) : a(a), b(b), c(std::move(c))
Yazmanın

9
Kabul edilen cevap, amacını bozar emplace_back. Bu doğru cevap. Bu nasıl emplace*çalışır. İletilen bağımsız değişkenleri kullanarak öğeyi yerinde oluştururlar. Bu nedenle, söz konusu argümanları almak için bir kurucuya ihtiyaç vardır.
underscore_d

1
yine de, vektör bir emplace_aggr sağlayabilir, değil mi?
tamas.kenez

@balki Sağ alarak anlamı yok ctarafından &&hiçbir şey olası rvalueness ile yapılır eğer; üyenin başlatıcısında, bir atama olmadığında argüman yine bir değer olarak değerlendirilir, böylece üye sadece kopya-yapılandırılır. Üye hareketle oluşturulmuş olsa bile, arayanların her zaman geçici veya std::move()d ldeğer geçmesini istemek deyimsel değildir (ancak bunu yaptığım kodumda birkaç köşe durumum olduğunu itiraf edeceğim, ancak yalnızca uygulama ayrıntılarında) .
underscore_d

26

Elbette bu bir cevap değil, ancak tuple'ların ilginç bir özelliğini gösteriyor:

#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
Kesinlikle bir cevap değil. bununla ilgili bu kadar ilginç olan ne? ctorlu herhangi bir tip bu şekilde yerleştirilebilir. tuple'ın bir ctoru vardır. operasyon yapısı yapmadı. işte cevabı.
underscore_d

6
@underscore_d: 3½ yıl önce düşündüğüm şeyin her ayrıntısını hatırladığımdan emin değilim, ancak önerdiğim şey, tuplebir POD yapısını tanımlamak yerine sadece a kullanırsanız, o zaman ücretsiz bir kurucu elde edersiniz. Bu, emplacesözdizimini ücretsiz olarak edindiğiniz anlamına gelir (diğer şeylerin yanı sıra - sözlüksel sıralama da elde edersiniz). Üye adlarını kaybedersiniz, ancak bazen erişimci oluşturmak, aksi takdirde ihtiyaç duyacağınız ortak metnin geri kalanından daha az zahmetlidir. Jerry Coffin'in cevabının kabul edilenden çok daha iyi olduğuna katılıyorum. Ben de yıllar önce ekledim.
rici

3
Evet, hecelemek ne demek istediğini anlamama yardımcı oluyor! İyi bir nokta. Bazen, STL'nin bize sunduğu diğer şeylere karşı tartıldığında genellemenin katlanılabilir olduğuna katılıyorum: Bunu yarı sık kullanıyorum pair... ama bazen net terimlerle gerçekten çok şey kazanıp kazanmadığımı merak ediyorum, heh. Ama belki tupleileride takip edecek. Genişlediğin için teşekkürler!
underscore_d

12

Bir kurucu eklemek istemiyorsanız (veya ekleyemiyorsanız), T için ayırıcıyı özelleştirin (veya kendi ayırıcınızı oluşturun).

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

Not: Yukarıda gösterilen üye işlevi yapısı clang 3.1 ile derlenemez (Üzgünüm, nedenini bilmiyorum). Clang 3.1 (veya başka nedenlerle) kullanacaksanız bir sonrakini deneyin.

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

Tahsis etme fonksiyonunuzda, hizalama konusunda endişelenmenize gerek yok mu? Bkzstd::aligned_storage
Andrew Tomazos

Sorun değil. Spesifikasyona göre, "void * :: operatörü new (size_t size)" nin etkileri, "Bu boyuttaki herhangi bir nesneyi temsil etmek için uygun şekilde hizalanmış depolama boyutu baytlarını tahsis etmek için yeni bir ifade tarafından çağrılan ayırma işlevi" dir.
Mitsuru Kariya

6

Bu, 23.2.1 / 13'te ele alınmış gibi görünüyor.

İlk olarak tanımlar:

A ile özdeş bir ayırıcı_tipi ve T ile özdeş bir değer_tipi olan ve A tipinde bir m değeri, T tipi bir p işaretçisi, T tipinde bir v ifadesi ve T tipi bir r değeri rv verildiğinde, X tipi bir konteyner verildiğinde, aşağıdaki terimler tanımlanmıştır.

Şimdi, onu yerleştirilebilir kılan şey:

T, sıfır veya daha fazla bağımsız değişken için args'den X'e EmplaceConstructible'dır, aşağıdaki ifadenin iyi biçimlendirildiği anlamına gelir: allocator_traits :: construct (m, p, args);

Ve son olarak, yapı çağrısının varsayılan uygulaması hakkında bir not:

Not: Bir konteyner, args kullanarak p'de bir eleman oluşturmak için allocator_traits :: construct (m, p, args) 'ı çağırır. Std :: allocator'daki varsayılan yapı :: new ((void *) p) T (args) 'ı çağıracaktır, ancak özel ayırıcılar farklı bir tanım seçebilir.

Bu bize, bir varsayılan (ve potansiyel olarak tek) ayırıcı şeması için, bir konteynere yerleştirmeye-inşa etmeye çalıştığınız şey için uygun sayıda argümana sahip bir kurucu tanımlamış olmanız gerektiğini söyler.


-2

türünüz için bir yapıcı tanımlamalısınız Tçünkü birstd::string önemsiz olmayan .

dahası, hareket ctor / atama tanımlaması (olası varsayılan) daha iyi olacaktır (çünkü std::stringüye olarak taşınabilir bir üyeye sahipsiniz ) - bu,T çok daha verimli ...

veya neighboug yanıtında önerildiği gibi sadece T{...}aşırı yüklü çağrı için kullanın emplace_back()... her şey tipik kullanım durumlarınıza bağlıdır ...



1
@ AndrewTomazos-Fathomling: yalnızca hiçbir kullanıcı tanımlanmamışsa
zaufi

1
Doğru ve değiller.
Andrew Tomazos

@ AndrewTomazos-Fathomling: ama aramada geçici bir emplace_back()
durumdan

1
Aslında yanlış. Yıkıcı, kopya yapıcı veya atama işleçlerinin tanımlanmaması koşuluyla, bir taşıma oluşturucu otomatik olarak oluşturulur. Emplace_back ile kullanılmak üzere 3 bağımsız değişkenli üye yapıcı tanımlamak, varsayılan taşıma yapıcısını bastırmaz.
Andrew Tomazos

-2

Örneği oluşturabilir struct Tve ardından vektöre taşıyabilirsiniz:

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

2
Geçici bir nesne T {...} std :: move () yapmanıza gerek yoktur. Zaten geçici bir nesnedir (rvalue). Böylece std :: move () 'i örneğinizden kaldırabilirsiniz.
Nadav Har'El

Dahası, T tip adı bile gerekli değildir - derleyici bunu tahmin edebilir. Yani sadece bir "V.push_back {42, 3.14," foo "}" çalışacaktır.
Nadav Har'El

-8

{}Yeni öğeyi başlatmak için sözdizimini kullanabilirsiniz :

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

Bu optimize edilmiş olabilir veya olmayabilir, ancak olması gerekir.

Bunun çalışması için bir kurucu tanımlamalısınız, kodunuzla şunları bile yapamayacağınızı unutmayın:

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

Ama işe yerleştirmek için ihtiyacın olan şey bu.

bu yüzden sadece:

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

istenilen şekilde çalışmasını sağlayacaktır.


10
Bu bir geçici oluşturacak ve sonra onu diziye taşıyacak mı? - yoksa öğeyi yerinde mi inşa edecek?
Andrew Tomazos

3
std::moveGerekli değildir. T{42, 3.14, "foo"}emplace_back tarafından zaten iletilecek ve struct move yapıcısına bir rvalue olarak bağlanacak. Ancak onu yerinde inşa eden bir çözümü tercih ederim.
Andrew Tomazos

37
bu durumda, hareket kopyalamaya neredeyse tamamen eşdeğerdir, bu nedenle yerleştirmenin tüm noktası gözden kaçar.
Alex I.

5
@AlexI. Aslında! Bu sözdizimi bir geçici oluşturur ve bu 'yerleştirme_geri' işlevine argüman olarak iletilir. Asıl noktayı tamamen kaçırıyor.
aldo

5
Tüm olumsuz geri bildirimleri anlamıyorum. Bu durumda derleyici RVO kullanmayacak mı?
Euri Pinhollow
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.