使用struct和std :: pair之间有什么区别?


26

我是经验有限的C ++程序员。

假设我想使用STL map存储和操作某些数据,我想知道这两种数据结构方法之间是否存在任何有意义的差异(在性能上):

Choice 1:
    map<int, pair<string, bool> >

Choice 2:
    struct Ente {
        string name;
        bool flag;
    }
    map<int, Ente>

具体来说,使用a struct而不是simple会产生任何开销pair吗?


18
A std::pair 一个结构。
卡雷斯(Caleth)'17

3
@gnat:对于像这样的特定问题,像这样的一般性问题很少是合适的被骗对象,尤其是如果在被骗对象上不存在特定答案(这种情况下不太可能)。
罗伯特·哈维

18
@Caleth- std::pair是一个模板std::pair<string, bool>是一个结构。
皮特·贝克尔

4
pair完全没有语义。e.first除非您明确指出,否则没有人阅读您的代码(包括将来的代码),不会知道这就是名称。我坚信这pair是对的补充std,而且非常懒惰,当它被构想时,没有人想到“但有一天,每个人都将用它来做两件事,而且没人会知道任何人的代码意味着什么”。
杰森C

2
@Snowman哦,当然。尽管如此,像map迭代器这样的事情并不是有效的异常,还是太糟糕了。(“第一个” =键,“第二个” =值...真的std吗?真的吗?)
Jason C

Answers:


33

对于小的“仅使用一次”的事情,选择1可以。本质std::pair上仍然是一个结构。如该评论所述,选择1将导致在兔子洞下方某处的代码非常丑陋,thing.second->first.second->second而没人真正想破译该代码。

选择2适用于其他所有条件,因为它更易于阅读地图中事物的含义。如果要更改数据(例如,当Ente突然需要另一个标志时),它也更加灵活。性能在这里不应该成为问题。


15

性能

这取决于。

在您的特定情况下,不会有性能差异,因为两者将类似地布置在内存中。

在非常特殊的情况下(如果您使用空结构作为数据成员之一),则该方法std::pair<>可能会利用空基优化(EBO),并且其大小要小于等效结构。较小的尺寸通常意味着更高的性能:

struct Empty {};
struct Thing { std::string name; Empty e; };

int main() {
    std::cout << sizeof(std::string) << "\n";
    std::cout << sizeof(std::tuple<std::string, Empty>) << "\n";
    std::cout << sizeof(std::pair<std::string, Empty>) << "\n";
    std::cout << sizeof(Thing) << "\n";
}

ideone上打印:32、32、40、40

注意:我不知道有任何实现实际上将EBO技巧用于常规对,但是通常将其用于元组。


可读性

除了微优化之外,命名结构更符合人体工程学。

我的意思是,map[k].first虽然不是很清楚,但get<0>(map[k])还不错。与之对比map[k].name立即表明我们正在阅读的内容。

当类型可以相互转换时,这一点尤为重要,因为无意间交换它们会成为一个真正的问题。

您可能还想阅读有关结构性打字与名义性打字的信息。Ente是一种特定类型,只能由期望的对象进行操作Ente,任何可以对其进行操作的对象std::pair<std::string, bool>都可以对它们进行操作...即使在std::stringbool不包含期望的对象时也是如此,因为std::pair它没有关联的语义


保养方式

在维护方面,pair是最糟糕的。您无法添加字段。

tuple在这方面,更好的做法是,只要您追加新字段,所有现有字段仍将由同一索引访问。像以前一样难以理解,但至少您不需要去更新它们。

struct是明显的赢家。您可以在任何需要的地方添加字段。


结论:

  • pair 是两全其美的
  • tuple 在特定情况下(空类型)可能会略有优势,
  • 使用struct

注意:如果您使用的是getters,那么您可以自己使用空的基本技巧,而客户不必像struct Thing: Empty { std::string name; };中那样了解它。这就是为什么封装是您应该关注的下一个主题的原因。


3
如果您遵循标准,则不能将EBO用于成对。对元素存储在会员 firstsecond,对空无处基地优化一命呜呼
Revolver_Ocelot

2
@Revolver_Ocelot:好的,您不能编写pair使用EBO 的C ++ ,但是编译器可以提供内置函数。但是,由于这些成员应该是成员,因此它可能是可观察到的(例如,检查其地址),在这种情况下,它将不符合要求。
Matthieu M.

1
C ++ 20增加了[[no_unique_address]],这使成员可以使用等效的EBO。
underscore_d

3

在用作函数的返回类型以及使用std :: tie和C ++ 17的结构化绑定进行的结构化赋值时,对最亮。使用std :: tie:

struct Ente {/*...*/};
std::map<int, Ente> map;
auto inserted_position = map.end();
auto was_inserted = false;
std::tie(inserted_position, was_inserted) = map.emplace(1, Ente{});
if (!was_inserted) {
    //handle insertion error
}

使用C ++ 17的结构化绑定:

struct Ente {/*...*/};
std::map<int, Ente> map;
auto [inserted_position, was_inserted] = map.emplace(1, Ente{});
if (!was_inserted) {
    //handle insertion error
}

一个使用std :: pair(或元组)的错误示例将是这样的:

using player_data = std::tuple<std::string, uint64_t, double>;
player_data player{};
/* ... */
auto health = std::get<2>(player);
/* ... */

因为在调用std :: get <2>(player_data)时尚不清楚位置索引2上存储的内容。请记住可读性并使读者清楚代码在做什么很重要。考虑到这更具可读性:

struct player_data
{
    std::string name;
    uint64_t player_id;
    double current_health;
};
player_data player{};
/* ... */
auto health = player.current_health;
/* ... */

通常,您应该考虑std :: pair和std :: tuple作为从函数返回多个对象的方法。我使用的经验法则(并且已经看到许多其他方法也使用)是,在调用返回该对象的函数的上下文中,在std :: tuple或std :: pair中返回的对象只是“相关”的或在将它们链接在一起的数据结构的上下文中(例如std :: map使用std :: pair作为其存储类型)。如果该关系在代码的其他位置存在,则应使用结构。

核心准则的相关部分:

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.