C ++ 11 vector <struct>上的emplace_back?


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
如果您提供适当的构造函数,那将起作用。
克里斯,2012年

2
有没有一种方法可以使用由自动使用的大括号struct构造函数就地构造它T t{42,3.14, "foo"}
安德鲁·托马佐斯

4
我不认为这是构造函数的形式。它是聚合初始化。
克里斯,2012年


5
我不会试图以任何方式影响您的意见。但是,如果您有一段时间没有关注细小的问题了。可能会误导读者。
Humam Helfawi,2016年

Answers:


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 2013年

9
被接受的答案破坏了目的emplace_back。这是正确的答案。这是emplace*工作方式。他们使用转发的参数就地构建元素。因此,需要一个构造函数来接受所述参数。
underscore_d

1
仍然,vector可以提供emplace_aggr,对吗?
tamas.kenez

@balki对了,还有没有点采取c通过&&,如果没有与它可能rvalueness完成的; 在成员的初始化程序中,在没有强制转换的情况下,该参数再次被视为左值,因此该成员仅是复制构造的。即使该成员是移动构造的,也不总是要求调用者始终传递一个临时std::move()值或d值(尽管我承认我的代码中有一些极端情况,但仅在实现细节中),这并不是惯常做法。
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。操作的结构没有。那就是答案。
underscore_d

6
@underscore_d:我不确定我是否记得3½年前我在想的每一个细节,但是我想告诉我的是,如果您只使用atuple而不是定义POD结构,那么您将免费获得一个构造函数,这意味着您可以emplace免费获得语法(此外,您还可以按字典顺序进行排序)。您会丢失成员名称,但有时创建访问器的麻烦程度要比您否则需要的所有其余样板麻烦。我同意杰里·科芬的答案比公认的要好得多。几年前,我也对此表示赞同。
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)... }; }
    };
}

注意:上面显示的成员函数构造无法使用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 2012年

没问题。根据规范,“ void * :: operator new(size_t size)”的效果是“由new表达式调用的分配函数,用于分配大小合适的存储字节,以适当对齐以表示该大小的任何对象。”
Mitsuru Kariya

6

这似乎在23.2.1 / 13中涵盖。

一,定义:

给定一个容器类型X,其容器分配器类型与A相同,值类型与T相同,并且给定一个类型A的左值m,一个类型T *的指针p,一个类型T的表达式v和一个类型T的右值rv,定义了以下术语。

现在,是什么使其可在位构建的:

T是从args到X的EmplaceConstructible,对于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()neighboug响应中建议的重载...一切都取决于您的典型用例...


自动为T生成了一个move构造函数
Andrew Tomazos 2012年

1
@ AndrewTomazos-Fathomling:仅当未定义用户
ctor时

1
正确,但事实并非如此。
安德鲁·托马佐斯

@ AndrewTomazos-Fathomling:但是您必须定义一些,以避免在emplace_back()调用时出现临时实例:)
zaufi 2012年

1
其实不正确。如果未定义析构函数,复制构造函数或赋值运算符,则会自动生成一个移动构造函数。定义3参数成员级构造函数以与emplace_back一起使用不会限制默认的move构造函数。
安德鲁·托马佐斯

-2

您可以创建struct T实例,然后将其移至向量:

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

2
您不需要std :: move()一个临时对象T {...}。它已经是一个临时对象(右值)。因此,您可以从示例中删除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
这会构造一个临时对象,然后将其构造到数组中吗?-还是会在原地构造商品?
安德鲁·托马佐斯

3
std::move是没有必要的。 T{42, 3.14, "foo"}将已经由emplace_back转发,并作为右值绑定到struct move构造函数。但是,我更喜欢一个就地构建它的解决方案。
安德鲁·托马佐斯

37
在这种情况下,移动几乎完全等同于复制,因此遗漏了整个重叠点。
Alex I.

5
@AlexI。确实!此语法创建一个临时文件,并将其作为参数传递给'emplace_back'。完全错了。
aldo 2013年

5
我不了解所有负面反馈。在这种情况下,编译器不会使用RVO吗?
Euri Pinhollow
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.