Jestem programistą C ++ z ograniczonym doświadczeniem.
Przypuśćmy, że chcę użyć STL map
do przechowywania i manipulowania niektórymi danymi, chciałbym wiedzieć, czy istnieje jakaś znacząca różnica (także w wydajności) między tymi dwoma podejściami do struktury danych:
Choice 1:
map<int, pair<string, bool> >
Choice 2:
struct Ente {
string name;
bool flag;
}
map<int, Ente>
W szczególności, czy jest jakiś narzut przy użyciu struct
zamiast zwykłego pair
?
c++
data-structures
stl
Marco Stramezzi
źródło
źródło
std::pair
jest strukturą.std::pair
to szablon .std::pair<string, bool>
jest strukturą.pair
jest całkowicie pozbawiony semantyki. Nikt nie czyta twojego kodu (w tym ciebie w przyszłości) nie będzie wiedział, żee.first
jest to nazwa czegoś, chyba że wyraźnie to zaznaczysz. Jestem głęboko przekonany, żepair
był to bardzo biedny i leniwy dodatek do tegostd
, i kiedy to zostało wymyślone, nikt nie pomyślał „ale pewnego dnia wszyscy wykorzystają to do wszystkiego , co jest dwiema rzeczami, i nikt nie będzie wiedział, co oznacza kod każdego „.map
iteratory nie są ważnymi wyjątkami. („pierwszy” = klucz i „drugi” = wartość ... naprawdęstd
? Naprawdę?)Odpowiedzi:
Wybór 1 jest odpowiedni dla małych „używanych tylko raz” rzeczy. Zasadniczo
std::pair
jest nadal strukturą. Jak stwierdzono w tym komentarzu, wybór 1 doprowadzi do naprawdę brzydkiego kodu gdzieś w dole króliczej nory,thing.second->first.second->second
i nikt tak naprawdę nie chce tego rozszyfrować.Wybór 2 jest lepszy do wszystkiego innego, ponieważ łatwiej jest odczytać, jakie są znaczenie rzeczy na mapie. Jest również bardziej elastyczny, jeśli chcesz zmienić dane (na przykład, gdy Ente nagle potrzebuje innej flagi). Wydajność nie powinna być tutaj problemem.
źródło
Wydajność :
To zależy.
W twoim konkretnym przypadku nie będzie różnicy w wydajności, ponieważ oba będą podobnie ułożone w pamięci.
W bardzo szczególnym przypadku (jeśli używałeś pustej struktury jako jednego z elementów danych), wówczas
std::pair<>
potencjalnie mogliby skorzystać z pustej optymalizacji bazy (EBO) i mieć mniejszy rozmiar niż odpowiednik struktury. A mniejszy rozmiar ogólnie oznacza wyższą wydajność:Drukuje: 32, 32, 40, 40 na ideone .
Uwaga: nie znam żadnej implementacji, która faktycznie używałaby sztuczki EBO dla zwykłych par, jednak ogólnie jest ona stosowana do krotek.
Czytelność :
Jednak oprócz mikrooptymalizacji nazwana struktura jest bardziej ergonomiczna.
Mam na myśli,
map[k].first
że nie jest tak źle, podczas gdyget<0>(map[k])
jest ledwo zrozumiałe. Kontrast zmap[k].name
którym natychmiast wskazuje, z czego czytamy.Jest to tym bardziej ważne, gdy typy są konwertowalne na siebie, ponieważ ich przypadkowa wymiana staje się prawdziwym problemem.
Możesz także przeczytać o typowaniu strukturalnym a typowym.
Ente
Jest to typ specyficzny, który może być obsługiwany tylko przez rzeczy, które oczekująEnte
, że wszystko może działać nastd::pair<std::string, bool>
może działać na nich ... nawet gdystd::string
lubbool
nie zawiera tego, co się spodziewać, ponieważstd::pair
nie ma semantykę z nim związane.Konserwacja :
Pod względem konserwacji
pair
jest najgorszy. Nie możesz dodać pola.tuple
targi są lepsze pod tym względem, o ile dodajesz nowe pole, wszystkie istniejące pola są nadal dostępne dla tego samego indeksu. Co jest tak nieprzeniknione jak wcześniej, ale przynajmniej nie musisz iść i je aktualizować.struct
jest wyraźnym zwycięzcą. Możesz dodawać pola w dowolnym miejscu.Podsumowując:
pair
jest najgorszy z obu światów,tuple
może mieć niewielką krawędź w bardzo szczególnym przypadku (pusty typ),struct
.Uwaga: jeśli używasz programów pobierających, możesz samodzielnie skorzystać z pustej sztuczki bazowej bez potrzeby, aby klienci wiedzieli o tym jak w
struct Thing: Empty { std::string name; }
; dlatego Encapsulation to kolejny temat, którym powinieneś się zająć.źródło
first
isecond
nie ma miejsca na rozpoczęcie Optymalizacji Pustej Bazy .pair
który używałby EBO, ale kompilator może zapewnić wbudowane. Ponieważ mają to być członkowie, może być jednak obserwowalne (na przykład sprawdzanie ich adresów), w którym to przypadku byłoby niezgodne.[[no_unique_address]]
, co umożliwia ekwiwalent EBO dla członków.Para świeci najbardziej, gdy jest używana jako typ zwracany funkcji wraz ze zniszczonym przypisaniem przy użyciu std :: tie i strukturalnego wiązania C ++ 17. Używając std :: tie:
Korzystanie z powiązania strukturalnego C ++ 17:
Zły przykład użycia std :: pair (lub krotki) może wyglądać następująco:
ponieważ nie jest jasne, kiedy wywołujemy std :: get <2> (data_gracza), co jest przechowywane w indeksie pozycji 2. Pamiętaj o czytelności i uświadomienie czytelnikowi, co robi kod, jest ważne . Weź pod uwagę, że jest to o wiele bardziej czytelne:
Ogólnie powinieneś pomyśleć o std :: pair i std :: tuple jako sposobach na zwrócenie więcej niż 1 obiektu z funkcji. Zasadą, której używam (i widziałem także wielu innych), jest to, że obiekty zwracane w std :: tuple lub std :: pair są „powiązane” tylko w kontekście wywołania funkcji, która je zwraca lub w kontekście struktury danych, która łączy je ze sobą (np. std :: map używa std :: pair dla swojego typu pamięci). Jeśli relacja istnieje gdzie indziej w kodzie, należy użyć struktury.
Powiązane sekcje podstawowych wytycznych:
źródło