Struct ve std :: pair kullanımı arasında ne fark var?


26

Sınırlı deneyime sahip bir C ++ programcısıyım.

Varsayalım ki STL map, bazı verileri depolamak ve değiştirmek için kullanmak istiyorum, bu 2 veri yapısı yaklaşımı arasında anlamlı bir fark olup olmadığını (performansta da) olup olmadığını bilmek isterim:

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

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

Özellikle, structbasit bir yerine bir ek yükü kullanan var pairmı?


18
Bir std::pair olan bir yapı.
Caleth

3
@gnat: Bunun gibi genel sorular nadiren uygun dupe hedefleri, özellikle de dupe hedefinde (bu durumda olması muhtemel olmayan) varsa, bunun gibi belirli sorular için uygundur.
Robert Harvey,

18
@Caleth - std::pairbir şablondur . std::pair<string, bool>bir yapıdır.
Pete Becker

4
pairtamamen anlamsallıktan yoksun. Kodunuzu okuyan hiç kimse (gelecekte de dahil olmak üzere), e.firstaçıkça belirtmediğiniz sürece, bunun bir şey olduğunu bilmeyecektir . Buna pairçok zayıf ve tembel bir katkı olduğuna stdve kimsenin düşünülmediğine inanıldığında kesin bir inancı duyuyorum "ama bir gün, herkes bunu iki şey olan her şey için kullanacak ve kimse kimsenin kodunun ne anlama geldiğini bilmeyecek ".
Jason C

2
@ Kardeş Ah, kesinlikle. Yine de, mapyineleyiciler gibi çok kötü şeyler geçerli istisnalar değildir. ("first" = anahtar ve "second" = değer ... gerçekten std
Jason C

Yanıtlar:


33

Seçim 1 küçük "sadece bir kez kullanılmış" şeyler için tamam. Temelde std::pairhala bir yapıdır. Bu yorumda belirtildiği gibi, seçim 1, tavşan deliğinden aşağıya bir yerlerde gerçekten çirkin bir kodla sonuçlanacak thing.second->first.second->secondve kimse bunu çözmek istemez.

Seçim 2, diğer her şey için daha iyidir, çünkü haritadaki şeylerin anlamını okumak kolaydır. Verileri değiştirmek istiyorsanız (örneğin, Ente bir anda başka bir bayrağa ihtiyaç duyduğunda) daha esnektir. Performans burada bir sorun olmamalıdır.


15

Performans :

Değişir.

Özel durumunuzda performans farkı olmayacak çünkü ikisi de hafızaya yerleştirilecek.

Çok özel bir durumda (eğer veri üyelerinden biri olarak boş bir yapı kullanıyorsanız ), o zaman std::pair<>potansiyel olarak Boş Temel Optimizasyonu (EBO) kullanabilir ve yapı eşdeğerinden daha düşük bir boyuta sahip olabilir. Ve düşük boyut genellikle daha yüksek performans anlamına gelir:

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

Baskı: 32, 32, 40, 40 ideone üzerinde .

Not: EBO hilesini normal çiftler için kullanan herhangi bir uygulamanın farkında değilim, ancak genellikle tuples için kullanılıyor.


Okunabilirlik :

Ancak, mikro optimizasyonların yanı sıra, adlandırılmış bir yapı daha ergonomiktir.

Yani, map[k].firsto kadar da kötü değil get<0>(map[k]), ancak anlaşılabilir bir durumdur. map[k].nameHangi ile hemen okuduğumuzu gösteren kontrast .

Bu tipler birbirlerine çevrilebilir olduğunda her şey daha önemlidir, çünkü onları yanlışlıkla değiştirmek gerçek bir endişe haline gelir.

Ayrıca Yapısal vs Nominal Yazma hakkında okumak isteyebilirsiniz. Enteyalnızca beklenen şeyler tarafından çalıştırılabilecek belirli bir türdür; üzerinde Enteçalışabilecek herhangi bir şey, üzerinde beklediklerini içermese veya içermese std::pair<std::string, bool>bile, üzerinde çalışabilir , çünkü onunla ilişkili hiçbir anlam ifade etmemektedir .std::stringboolstd::pair


Bakım :

Bakım açısından pairen kötüsüdür. Bir alan ekleyemezsiniz.

tupleYeni alanı eklediğiniz sürece mevcut tüm alanlara aynı endeks ile erişilir. Bu eskisi kadar esrarengiz ama en azından onları güncellemene gerek yok.

structaçık kazanan. Dilediğiniz yere alanlar ekleyebilirsiniz.


Sonuç olarak:

  • pair her iki dünyanın da en kötüsü
  • tuple çok özel bir durumda küçük bir kenarı olabilir (boş tip),
  • kullanınstruct .

Not: Alıcıları kullanıyorsanız, boş üs kandırmasını, müşteriler de olduğu gibi bilmek zorunda kalmadan kullanabilirsiniz struct Thing: Empty { std::string name; }; Bu yüzden Kapsülleme , ilgileneceğiniz bir sonraki konu.


3
Standardı takip ediyorsanız EBO'yu çiftler için kullanamazsınız. Çiftin elementleri üyelerde depolanır firstve secondBoş Temel Optimizasyonun
Revolver_Ocelot

2
@Revolver_Ocelot: Eh, EBO kullanacak bir C ++ yazamazsınızpair , ancak bir derleyici yerleşik bir dosya sağlayabilir. Ancak, bunların üye olması gerektiğinden, bu durumda uygun olmayacağı gözlemlenebilir (örneğin adreslerini kontrol ediyor) olabilir.
Matthieu M.

1
C ++ 20 [[no_unique_address]], üyeler için EBO'nun eşdeğerini sağlayan ekler .
underscore_d

3

Pair, bir fonksiyonun geri dönüş tipi olarak kullanıldığında en çok, std :: tie ve C ++ 17'nin yapısal bağını kullanarak tahribatlı atamalarla birlikte parlar. Std :: tie kullanarak:

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'nin yapılandırılmış bağını kullanarak:

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

Bir std :: pair (veya tuple) kullanımının kötü bir örneği şöyle olabilir:

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

std :: get <2> çağrılırken o belli değil çünkü (player_data) okunabilirliği ve kodudur ne yaptığını okuyucu için bu bariz hale hatırla 2. pozisyon dizinindeki neyi önemli . Bunun daha okunaklı olduğunu düşünün:

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

Genel olarak, bir fonksiyondan 1'den fazla nesneyi döndürmenin yolu olarak std :: pair ve std :: tuple'ı düşünmelisiniz. Kullandığım başparmak kuralı (ve diğerlerinin de kullandığını gördük), bir std :: tuple veya std :: pair olarak döndürülen nesnelerin, onları döndüren bir fonksiyona çağrı yapma bağlamında sadece "ilişkili" olduklarıdır. veya bunları birbirine bağlayan veri yapısı bağlamında (örneğin std :: map, depolama türü için std :: pair kullanır). İlişki kodunuzda başka bir yerde mevcutsa, bir yapı kullanmalısınız.

Çekirdek İlkelerin ilgili bölümleri:

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.