什么是std::pair?

45

std::pair是什么,我为什么要使用它,以及boost::compressed_pair有什么好处?

10个回答

81

compressed_pair 使用模板技巧来节省空间。在 C++ 中,一个对象(小写字母 o)不能和另一个不同的对象有相同的地址。

因此,即使你有

struct A { };

A的大小不会为0,否则:

A a1;
A a2;
&a1 == &a2;

会发生非法操作,因为变量持有一个不被允许的值。

但是很多编译器都会执行所谓的“空基类优化”:

struct A { };
struct B { int x; };
struct C : public A { int x; };

这里,即使sizeof(A)不能为零,BC具有相同的大小也是可以的。

因此,boost::compressed_pair 利用了这个优化,在可能的情况下,如果一对类型中有一个为空,它将继承自其中一个类型。

所以,一个 std::pair 可能看起来像这样(省略了许多内容,如构造函数等):

template<typename FirstType, typename SecondType>
struct pair {
   FirstType first;
   SecondType second;
};

这意味着如果FirstTypeSecondType之一是A,则您的pair<A,int>必须大于sizeof(int)

但是,如果您使用compressed_pair,它生成的代码将类似于:

 struct compressed_pair<A,int> : private A {
    int second_;
    A first() { return *this; }
    int second() { return second_; }
 };

compressed_pair<A,int> 的大小只有 sizeof(int)。


1
刚刚看到这篇文章,心想这个人的回答思路清晰、完整,然后才发现是谁写的,一点也不惊讶!嘿,Logan! - Dominic Rodger
除非有两个洛根·卡帕尔多,否则洛根就是我一起工作的人。 - Dominic Rodger
1
据我所知,我没有分身。嗨,Dom! :) - Logan Capaldo
6
另外,我想补充一点,如果你不进行一些复杂的泛型编程,那么compressed_pair就完全没有用处,因为在现实生活中,没有任何情况下你会将一个没有数据的类放入pair中。 - Viktor Sehr
1
@ViktorSehr 看起来这对于存储智能指针类的自定义删除器/克隆器可能很有用。通常,您不会在类中拥有任何数据成员,但仍需要支持有状态的分配器/释放器。这就是我在这里寻找的。我认为这不是“繁重”的泛型编程。 - David Stone
显示剩余2条评论

35

std::pair 是一种数据类型,用于将两个值组合成一个对象。 std::map 将其用于键值对。

在学习pair时,您可能会查看tuple。它类似于pair,但用于组合任意数量的值。tuple是TR1的一部分,许多编译器已经将其包含在其标准库实现中。

此外,请查看Pete Becker的书《C++标准库扩展:教程和参考资料》第1章“元组”(ISBN-13: 9780321412997)以获得详细解释。

alt text


1
此外,如果你的编译器还没有支持TR1的话,Boost库有一个很好的tuple实现。 - J T

12
你有时需要从函数返回2个值,为此创建一个类通常是过度设计。
在这种情况下,std:pair非常有用。
我认为boost:compressed_pair能够优化大小为0的成员。这在库中使用重型模板机制时非常有用。
如果你直接控制类型,这就无关紧要了。

3
你可以使用boost::tie来编写近似于"a,b = func();"的代码。不必显式地实例化一个pair,只需写成:"boost::tie(a,b) = func();"即可。 - rlerallut

11

听到压缩对要注意几个字节可能听起来很奇怪。但是当考虑到压缩对可以使用的地方时,这实际上可能非常重要。例如,让我们考虑以下代码:

boost::function<void(int)> f(boost::bind(&f, _1));

在像上面那样的情况下使用compressed_pair可能会突然产生很大的影响。如果boost::bind将函数指针和占位符"_1"作为其自身或std::pair的成员存储,会发生什么?嗯,它可能会膨胀到sizeof(&f)+sizeof(_1)。假设函数指针有8个字节(特别是对于成员函数来说并不罕见),占位符有一个字节(请参见Logan的答案),则我们可能需要9个字节来绑定对象。由于对齐的原因,在通常的32位系统上这可能会膨胀到12个字节。
boost::function鼓励其实现应用小对象优化。这意味着对于小型函数对象,直接嵌入在boost::function对象中的小缓冲区用于存储函数对象。对于较大的函数对象,必须使用堆通过使用operator new获取内存。在boost 版本1.34左右,决定采用此优化,因为人们认为可以获得一些非常大的性能优势。

现在,对于这样一个小缓冲区来说,一个合理的(尽管可能仍然很小)限制应该是8个字节。也就是说,我们相当简单的绑定对象不适合放入小缓冲区中,并且需要使用operator new进行存储。如果上述绑定对象使用了compressed_pair,它实际上可以将其大小减小到8个字节(对于非成员函数指针通常为4个字节),因为占位符只是一个空对象。

因此,看起来只是为了节省一些字节而浪费了很多思考,实际上可能会对性能产生重大影响。


3

std::pair是什么,我为什么要使用它?

它只是一个简单的两个元素的元组。它是在STL的第一个版本中定义的,当时编译器不广泛支持模板和元编程技术,这些技术需要实现更复杂类型的元组,如Boost.Tuple

它在许多情况下都很有用。 std::pair在标准关联容器中使用。它可以被用作范围的简单形式 std::pair<iterator, iterator> - 因此可以定义接受单个表示范围的对象的算法,而不是分别定义两个迭代器。 (在许多情况下,这是一个有用的替代品。)


3

std::pair在STL中的其他一些容器类中非常方便。

例如:

std::map<>
std::multimap<> 

这两者都存储键和值的 std::pair。

当使用 map 和 multimap 时,通常使用指向 pair 的指针来访问元素。


3

这是用于存储一对值的标准类。它被一些标准函数(如std::map::insert)返回/使用。

boost::compressed_pair声称更加高效:点击此处了解更多信息。


3

附加信息:当一对中的一个类型是空结构体时,boost::compressed_pair非常有用。在模板元编程中经常使用此功能,当从其他类型程序化推断出一对的类型时。最后,通常会得到某种形式的“空结构体”。

除非您进行大量的模板元编程,否则我更喜欢std::pair用于任何“正常”用途。


3

实际上这只是一种带有两个变量的结构。

我不喜欢在函数返回时使用std::pair。代码的读者需要知道first和second分别代表什么意思。

有时我会做一个妥协,立即创建对first和second的常量引用,并清晰地命名这些引用。


1
我同意你关于命名规范的看法。这会使维护变得更加困难。 - Adam

1
有时候,有两个信息总是一起传递,无论是作为参数、返回值或其他。当然,你可以编写自己的对象,但如果只是两个小的基本类型或类似的东西,有时候一个二元组看起来就很好。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接