Apa perbedaan antara menggunakan struct dan std :: pair?

26

Saya seorang programmer C ++ dengan pengalaman terbatas.

Andaikata saya ingin menggunakan suatu STL mapuntuk menyimpan dan memanipulasi beberapa data, saya ingin tahu apakah ada perbedaan yang berarti (juga dalam kinerja) antara kedua pendekatan struktur data tersebut:

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

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

Secara khusus, apakah ada overhead menggunakan structbukan sederhana pair?

Marco Stramezzi
sumber
18
A std::pair adalah sebuah struct.
Caleth
3
@gnat: Pertanyaan umum seperti itu jarang menjadi target dupe yang cocok untuk pertanyaan spesifik seperti ini, terutama jika jawaban spesifik tidak ada pada target dupe (yang tidak mungkin dalam kasus ini).
Robert Harvey
18
@Caleth - std::pairadalah templat . std::pair<string, bool>adalah sebuah struct.
Pete Becker
4
pairsepenuhnya tanpa semantik. Tidak ada yang membaca kode Anda (termasuk Anda di masa depan) akan tahu itu e.firstadalah nama sesuatu kecuali Anda secara eksplisit menunjukkannya. Saya sangat percaya bahwa pairitu adalah tambahan yang sangat miskin dan malas std, dan bahwa ketika dikandung tidak ada yang berpikir "tetapi suatu hari, semua orang akan menggunakan ini untuk segala sesuatu yang dua hal, dan tidak ada yang akan tahu apa arti kode siapa pun ".
Jason C
2
@Snowman Oh, tentu saja. Namun, itu hal yang terlalu buruk seperti mapiterator bukan pengecualian yang valid. ("pertama" = kunci dan "kedua" = nilai ... sungguh std,? Benarkah?)
Jason C

Jawaban:

33

Pilihan 1 ok untuk hal-hal "digunakan hanya sekali" kecil. Pada dasarnya std::pairmasih sebuah struct. Seperti yang dinyatakan oleh komentar ini, pilihan 1 akan mengarah ke kode yang sangat jelek di suatu tempat di bawah lubang kelinci seperti thing.second->first.second->seconddan tidak ada yang benar-benar ingin menguraikan itu.

Pilihan 2 lebih baik untuk yang lainnya, karena lebih mudah untuk membaca apa arti dari hal-hal di peta. Ini juga lebih fleksibel jika Anda ingin mengubah data (misalnya ketika Ente tiba-tiba membutuhkan bendera lain). Kinerja seharusnya tidak menjadi masalah di sini.

meningkatnya Darkness
sumber
15

Kinerja :

Tergantung.

Dalam kasus khusus Anda, tidak akan ada perbedaan kinerja karena keduanya akan diletakkan dalam memori yang sama.

Dalam kasus yang sangat spesifik (jika Anda menggunakan struct kosong sebagai salah satu anggota data) maka std::pair<>berpotensi menggunakan Empty Base Optimization (EBO) dan memiliki ukuran yang lebih rendah daripada setara struct. Dan ukuran yang lebih rendah umumnya berarti kinerja yang lebih tinggi:

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

Cetakan: 32, 32, 40, 40 di ideone .

Catatan: Saya tidak mengetahui adanya implementasi yang benar-benar menggunakan trik EBO untuk pasangan reguler, namun umumnya digunakan untuk tupel.


Keterbacaan :

Terlepas dari optimasi mikro, struktur bernama lebih ergonomis.

Maksudku, map[k].firsttidak seburuk itu sementara get<0>(map[k])hampir tidak bisa dimengerti. Kontras dengan map[k].nameyang segera menunjukkan apa yang kita baca.

Ini semua menjadi lebih penting ketika tipe-tipe tersebut dapat dipertukarkan satu sama lain, karena bertukar secara tidak sengaja menjadi perhatian nyata.

Anda mungkin juga ingin membaca tentang Pengetikan Struktural vs Nominal. Enteadalah jenis khusus yang hanya dapat dioperasikan oleh hal-hal yang diharapkan Ente, apa pun yang dapat beroperasi std::pair<std::string, bool>dapat beroperasi pada mereka ... bahkan ketika std::stringatau booltidak berisi apa yang mereka harapkan, karena std::pairtidak memiliki semantik yang terkait dengannya.


Perawatan :

Dalam hal perawatan, pairadalah yang terburuk. Anda tidak dapat menambahkan bidang.

tuplelebih baik dalam hal itu, selama Anda menambahkan bidang baru semua bidang yang ada masih diakses oleh indeks yang sama. Yang tidak dapat dipahami seperti sebelumnya tetapi setidaknya Anda tidak perlu pergi dan memperbaruinya.

structadalah pemenang yang jelas. Anda dapat menambahkan bidang di mana pun Anda suka.


Kesimpulannya:

  • pair adalah yang terburuk dari kedua dunia,
  • tuple mungkin memiliki sedikit keunggulan dalam kasus yang sangat spesifik (tipe kosong),
  • gunakanstruct .

Catatan: jika Anda menggunakan getter, maka Anda dapat menggunakan trik basis kosong sendiri tanpa klien harus mengetahuinya seperti pada struct Thing: Empty { std::string name; }; itulah sebabnya enkapsulasi adalah topik berikutnya yang harus Anda perhatikan sendiri.

Matthieu M.
sumber
3
Anda tidak dapat menggunakan EBO untuk berpasangan, jika Anda mengikuti Standar. Elemen pasangan disimpan dalam anggota first dan second, tidak ada tempat untuk Empty Base Optimization untuk memulai.
Revolver_Ocelot
2
@Revolver_Ocelot: Ya, Anda tidak dapat menulis C ++ pairyang akan menggunakan EBO, tetapi kompiler dapat menyediakan built-in. Namun, karena mereka dianggap anggota, maka dapat diamati (memeriksa alamat mereka, misalnya) dalam hal ini tidak akan sesuai.
Matthieu M.
1
C ++ 20 menambahkan [[no_unique_address]], yang memungkinkan setara dengan EBO untuk anggota.
underscore_d
3

Pair paling bersinar ketika digunakan sebagai tipe pengembalian fungsi bersama dengan penugasan yang dirusak menggunakan std :: tie dan C ++ 17's binding binding. Menggunakan 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
}

Menggunakan ikatan terstruktur C ++ 17:

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

Contoh buruk penggunaan std :: pair (atau tuple) akan menjadi seperti ini:

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

karena tidak jelas ketika memanggil std :: get <2> (player_data) apa yang disimpan di indeks posisi 2. Ingat keterbacaan dan membuatnya jelas bagi pembaca apa yang dilakukan kode itu penting . Pertimbangkan bahwa ini jauh lebih mudah dibaca:

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

Secara umum Anda harus berpikir tentang std :: pair dan std :: tuple sebagai cara untuk mengembalikan lebih dari 1 objek dari suatu fungsi. Aturan praktis yang saya gunakan (dan telah melihat banyak orang lain juga menggunakan) adalah bahwa objek dikembalikan dalam std :: tuple atau std :: pair hanya "terkait" dalam konteks membuat panggilan ke fungsi yang mengembalikannya atau dalam konteks struktur data yang menautkan keduanya (mis. std :: map menggunakan std :: pair untuk tipe penyimpanannya). Jika hubungan ada di tempat lain dalam kode Anda, Anda harus menggunakan struct.

Bagian terkait dari Pedoman Inti:

Damian Jarek
sumber