C ++ 11 emplace_back on vector <struct>?


87

다음 프로그램을 고려하십시오.

#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

2
에서 사용하는 자동으로 생성 된 중괄호 구조체 생성자를 사용하여 제자리에서 구성하는 방법이 T t{42,3.14, "foo"}있습니까?
앤드류 Tomazos

4
나는 그것이 생성자의 형태를 취한다고 생각하지 않습니다. 집계 초기화입니다.
chris


5
나는 어떤 식 으로든 당신의 의견에 영향을 주려고하지 않습니다. 그러나 당신이 한동안 얇은 질문에주의를 기울이지 않았다면 .. 그 작가에 대한 완전한 존경심에서 받아 들여진 대답은 당신의 질문에 대한 대답이 아닙니다. 독자를 오도 할 수 있습니다.
Humam Helfawi 2016

답변:


18

미래의 누구에게나이 동작 C ++ 20 에서 변경 될 것 입니다.

즉, 구현이 내부적으로 여전히 호출 되더라도 예상 할 수있는 T(arg0, arg1, ...)규칙적인 것으로 간주됩니다 T{arg0, arg1, ...}.


93

클래스에 대한 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&&아무것도가 가능 rvalueness으로 수행되지 않은 경우; 멤버의 이니셜 라이저에서 인수는 캐스트가 없을 때 다시 lvalue로 처리되므로 멤버는 복사 생성됩니다. 멤버가 이동 생성 된 경우에도 호출자에게 항상 임시 또는 std::move()d lvalue 를 전달하도록 요구하는 것은 관용적이지 않습니다 (내 코드에 몇 가지 코너 케이스가 있음을 고백하지만 구현 세부 사항에서만) .
underscore_d

25

물론 이것은 대답이 아니지만 튜플의 흥미로운 기능을 보여줍니다.

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

8
확실히 대답이 아닙니다. 뭐가 그렇게 흥미로운가요? ctor가있는 모든 유형은 이런 방식으로 배치 될 수 있습니다. 튜플에는 ctor가 있습니다. op의 구조체는 그렇지 않았습니다. 그게 답입니다.
underscore_d

6
@underscore_d : 제가 3 년 반 전에 생각했던 모든 세부 사항을 기억하고 있는지는 모르겠지만 제가 제안한 것은 tuplePOD 구조체를 정의하는 대신 a 를 사용 하면 생성자를 무료로 얻는다는 것입니다. , 즉, emplace무료로 구문을 얻을 수 있습니다 (다른 것 중에서도 사전 순서도 제공됨). 멤버 이름을 잃어 버리지 만 때로는 필요한 나머지 상용구보다 접근자를 만드는 것이 덜 귀찮습니다. 나는 Jerry Coffin의 대답이 받아 들여진 것보다 훨씬 낫다는 데 동의합니다. 나는 또한 몇 년 전에 그것을 upvoted.
rici 2016-07-13

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 (또는 다른 이유)을 사용하려면 다음을 시도하십시오.

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)"의 효과는 "그 크기의 객체를 나타 내기 위해 적절하게 정렬 된 스토리지의 크기 바이트를 할당하기 위해 new-expression에 의해 호출되는 할당 함수"입니다.
Mitsuru Kariya

6

이것은 23.2.1 / 13에서 다루는 것 같습니다.

첫째, 정의 :

A와 동일한 allocator_type 및 T와 동일한 value_type을 가지며 A 유형의 lvalue m, 유형 T *의 포인터 p, 유형 T의 표현식 v 및 유형 T의 rvalue rv를 갖는 컨테이너 유형 X가 주어지면, 다음 용어가 정의됩니다.

이제 emplace-constructible로 만드는 것은 무엇입니까?

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.

더욱이, 이동 ctor / 할당을 정의하는 것이 더 좋을 것입니다 (기본적으로 가능) 이동 ctor / 할당 ( std::string멤버로서 이동식이 있기 때문에 )-이것은 T훨씬 더 효율적 으로 이동하는 데 도움이 될 것입니다 .

또는 neighboug 응답에서 권장하는대로 T{...}오버로드 emplace_back()된 호출에 사용 합니다 ... 모든 것은 일반적인 사용 사례에 따라 다릅니다 ...


이동 생성자는 자동으로 T 생성됩니다
앤드류 Tomazos에게

1
@ AndrewTomazos-Fathomling : 사용자 ctors가 정의되어 있지 않은 경우에만
zaufi

1
정확하고 그렇지 않습니다.
Andrew Tomazos dec.

@ AndrewTomazos-Fathomling :하지만 당신은 임시 인스턴스를 방지하기 위해, 몇 가지를 정의해야합니다 emplace_back(): 전화
zaufi

1
사실 틀 렸습니다. 소멸자, 복사 생성자 또는 할당 연산자가 정의되지 않은 경우 이동 생성자가 자동으로 생성됩니다. emplace_back과 함께 사용할 3 인수 멤버 별 생성자를 정의하면 기본 이동 생성자가 억제되지 않습니다.
앤드류 Tomazos

-2

struct T인스턴스를 만든 다음 벡터로 이동할 수 있습니다 .

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

2
임시 객체 T {...}를 std :: move () 할 필요가 없습니다. 이미 임시 객체 (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");

그러나 이것이 당신이 emplace 일을하기 위해 필요한 것입니다.

그래서 그냥:

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

원하는 방식으로 작동합니다.


10
이 구성은 임시 구성한 다음 구성을 배열로 이동합니까? -아니면 제자리에 항목을 구성합니까?
앤드류 Tomazos

3
std::move필요하지 않습니다. T{42, 3.14, "foo"}이미 emplace_back에 의해 전달되고 구조체 이동 생성자에 rvalue로 바인딩됩니다. 그러나 나는 그것을 제자리에서 구성하는 솔루션을 선호합니다.
앤드류 Tomazos

37
이 경우 이동은 복사와 거의 정확히 동일하므로 전체 배치 지점이 누락됩니다.
Alex I.

5
@AlexI. 과연! 이 구문은 'emplace_back'에 인수로 전달되는 임시를 만듭니다. 요점을 완전히 놓친다.
aldo

5
나는 모든 부정적인 피드백을 이해하지 못합니다. 이 경우 컴파일러는 RVO를 사용하지 않습니까?
Euri Pinhollow 2017-04-23
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.