vector <struct>のC ++ 11 emplace_back?


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 2012

3
によって使用される自動的に作成された中括弧構造体コンストラクターを使用してインプレースで構築する方法はありT t{42,3.14, "foo"}ますか?
Andrew Tomazos 2012

4
それがコンストラクターの形をとるとは思いません。集約初期化です。
chris 2012


5
私は決してあなたの意見に影響を与えようとはしていません。しかし、あなたがしばらくの間薄い質問に注意を払わなかった場合..受け入れられた答えは、その作者に完全に敬意を表して、あなたの質問に対する答えではありません。読者を誤解させる可能性があります。
Humam Helfawi 2016年

回答:


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 2013

9
受け入れられた答えは、の目的を打ち破りemplace_backます。これが正解です。これがどのようにemplace*機能するかです。転送された引数を使用して、要素をインプレースで構築します。したがって、前述の引数を取るにはコンストラクターが必要です。
underscore_d

1
それでも、vectorはemplace_aggrを提供できますよね?
tamas.kenez 2017

@balkiそうですね、その可能性のある価値観で何もc&&なければ、意味がありません。メンバーのイニシャライザーでは、キャストがない場合、引数は再び左辺値として扱われるため、メンバーはコピーで構成されます。メンバーがmove-constructedであったとしても、呼び出し元に常に一時std::move()値またはd左辺値を渡すように要求するのは慣用的ではありません(ただし、コードにはいくつかのコーナーケースがありますが、実装の詳細のみです) 。
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年半前に考えていたことの詳細をすべて覚えているかどうかはわかりませんがtuple、POD構造体を定義する代わりに使用するだけで、コンストラクターを無料で入手できることを示唆していました。、これは、emplace構文を無料で入手できることを意味します(とりわけ、辞書式順序も入手できます)。メンバー名は失われますが、アクセサを作成する手間が、他の方法で必要になる他のすべての定型文よりも少なくなる場合があります。私は、ジェリー・コフィンの答えが受け入れられたものよりもはるかに優れていることに同意します。私も何年も前にそれを賛成しました。
rici 2016

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

注:上記のメンバー関数構造は、clang 3.1ではコンパイルできません(申し訳ありませんが、理由はわかりません)。clang 3.1(またはその他の理由)を使用する場合は、次の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 2012

問題ない。仕様によると、「void * :: operator new(size_t size)」の効果は、「そのサイズのオブジェクトを表すために適切に配置されたストレージのサイズバイトを割り当てるためにnew-expressionによって呼び出される割り当て関数」です。
刈谷満

6

これは23.2.1 / 13でカバーされているようです。

まず、定義:

Aと同一のallocator_typeとTと同一のvalue_typeを持ち、タイプAの左辺値m、タイプT *のポインターp、タイプTの式v、およびタイプTの右辺値rvを持つコンテナータイプXが与えられた場合、以下の用語が定義されています。

さて、何がそれを据え付け可能にするのか:構築可能:

TはargsからXにEmplaceConstructibleであり、0個以上の引数argsの場合、次の式が整形式であることを意味します。allocator_traits:: construct(m、p、args);

そして最後に、構成呼び出しのデフォルトの実装に関する注意:

注:コンテナーはallocator_traits :: construct(m、p、args)を呼び出して、argsを使用してpに要素を作成します。std :: allocatorのデフォルトの構成は、:: new((void *)p)T(args)を呼び出しますが、特殊なアロケーターは別の定義を選択する場合があります。

これは、デフォルトの(そして潜在的に唯一の)アロケータースキームの場合、コンテナーに配置しようとしているものに対して適切な数の引数を使用してコンストラクターを定義している必要があることを示しています。


-2

タイプにはコンストラクターがT含まれているため、コンストラクターを定義する必要があります。std::string自明ではない。

さらに、(デフォルトの可能性がある)move ctor / assignを定義することをお勧めします(std::stringメンバーとして可動式があるため)-これは、Tはるかに効率的...

または、近隣の応答で推奨されT{...}ているようにオーバーロードを呼び出すために使用しますemplace_back()...すべては典型的なユースケースに依存します...


Tの移動コンストラクターが自動的に生成されます
Andrew Tomazos 2012

1
@ AndrewTomazos-Fathomling:ユーザーctorが定義されていない場合のみ
zaufi 2012

1
正解ですが、そうではありません。
Andrew Tomazos 2012

@ AndrewTomazos-Fathomling:ただし、emplace_back()呼び出し時の一時的なインスタンスを回避するために、いくつかを定義する必要があります:)
zaufi 2012

1
実際には正しくありません。デストラクタ、コピーコンストラクタ、または代入演算子が定義されていない場合、移動コンストラクタは自動的に生成されます。emplace_backで使用する3引数のメンバーごとのコンストラクターを定義しても、デフォルトの移動コンストラクターは抑制されません。
Andrew Tomazos 2012

-2

struct Tインスタンスを作成してから、ベクターに移動できます。

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

2
一時オブジェクトT {...}をstd :: move()する必要はありません。すでに一時オブジェクト(右辺値)です。したがって、例からstd :: move()を削除するだけです。
NadavHar'El20年

さらに、型名Tでさえ必要ありません-コンパイラはそれを推測できます。したがって、「V.push_back {42、3.14、 "foo"}」だけが機能します。
NadavHar'El20年

-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 2012

3
std::move必要はありません。 T{42, 3.14, "foo"}すでにemplace_backによって転送され、構造体移動コンストラクターに右辺値としてバインドされます。しかし、私はそれをインプレースで構築するソリューションを好みます。
Andrew Tomazos 2012

37
この場合、移動はコピーとほぼ同じであるため、配置のポイント全体が失われます。
Alex I.

5
@AlexI。確かに!この構文は一時的なものを作成し、それは引数として 'emplace_back'に渡されます。完全にポイントを逃します。
aldo 2013

5
私はすべての負帰還を理解していません。この場合、コンパイラはRVOを使用しませんか?
Euri Pinhollow 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.