Я программист C ++ с ограниченным опытом.
Предположим, что я хочу использовать STL map
для хранения и манипулирования некоторыми данными, я хотел бы знать, есть ли существенное различие (также в производительности) между этими двумя подходами структуры данных:
Choice 1:
map<int, pair<string, bool> >
Choice 2:
struct Ente {
string name;
bool flag;
}
map<int, Ente>
В частности, есть ли накладные расходы, используя struct
вместо простого pair
?
c++
data-structures
stl
Марко Страмецци
источник
источник
std::pair
Это на структуру.std::pair
это шаблон .std::pair<string, bool>
это структура.pair
полностью лишен семантики. Никто, читающий ваш код (включая вас в будущем), не узнает, чтоe.first
это имя чего-либо, если вы явно не укажете это. Я твердо верю в то, что этоpair
было очень плохое и ленивое дополнениеstd
, и что, когда оно было задумано, никто не думал, «но однажды все будут использовать это для всего, что является двумя вещами, и никто не будет знать, что означает чей-либо код ».map
итераторы не являются допустимыми исключениями. ("first" = ключ и "second" = значение ... действительно,std
правда?)Ответы:
Вариант 1 подходит для небольших «используемых только один раз» вещей. По сути
std::pair
все еще структура. Как указано в этом комментарии, выбор 1 приведет к действительно ужасному коду где-то внизу кроличьей норы,thing.second->first.second->second
и никто не хочет его расшифровать.Вариант 2 лучше для всего остального, потому что легче понять, что означают вещи на карте. Это также более гибко, если вы хотите изменить данные (например, когда Ente внезапно понадобится другой флаг). Производительность не должна быть проблемой здесь.
источник
Производительность :
Это зависит.
В вашем конкретном случае не будет никакой разницы в производительности, потому что эти два будут аналогично размещены в памяти.
В очень конкретном случае (если вы использовали пустую структуру в качестве одного из элементов данных), она
std::pair<>
может потенциально использовать Оптимизацию пустой базы (EBO) и иметь меньший размер, чем эквивалент структуры. А меньший размер обычно означает более высокую производительность:Отпечатки: 32, 32, 40, 40 на ideone .
Примечание. Мне неизвестно о какой-либо реализации, которая на самом деле использует трюк EBO для обычных пар, однако обычно он используется для кортежей.
Читаемость :
Однако, кроме микрооптимизации, именованная структура более эргономична.
Я имею в виду,
map[k].first
это не так уж плохо, покаget<0>(map[k])
едва понятно. Контраст сmap[k].name
которым сразу указывает на то, с чего мы читаем.Это тем более важно, когда типы могут быть преобразованы друг в друга, поскольку их случайная замена становится реальной проблемой.
Вы могли бы также хотеть прочитать о Структурном против Номинального Печатания.
Ente
это определенный тип, который может работать только с теми вещами, которые ожидаютEnte
, все, с чем он может работать,std::pair<std::string, bool>
может воздействовать на них ... даже когдаstd::string
илиbool
не содержит того, что они ожидают, потому чтоstd::pair
с ним не связано никакой семантики .Техническое обслуживание :
С точки зрения обслуживания,
pair
это худшее. Вы не можете добавить поле.tuple
В этом отношении лучше, если вы добавите новое поле, все существующие поля будут по-прежнему доступны по одному и тому же индексу. Что так же непостижимо, как и раньше, но, по крайней мере, вам не нужно идти и обновлять их.struct
явный победитель. Вы можете добавлять поля везде, где хотите.В заключение:
pair
худший из обоих миров,tuple
может иметь небольшое преимущество в очень специфическом случае (пустой тип),struct
.Примечание: если вы используете геттеры, то вы можете использовать пустую базовую уловку самостоятельно, чтобы клиенты не знали об этом, как в
struct Thing: Empty { std::string name; }
; вот почему инкапсуляция - это следующая тема, о которой вам следует задуматься.источник
first
иsecond
для оптимизации пустой базы нет места .pair
который бы использовал EBO, но компилятор мог бы предоставить встроенный. Однако поскольку предполагается, что они являются членами, это может быть наблюдаемым (например, проверка их адресов), и в этом случае оно не будет соответствовать.[[no_unique_address]]
, что позволяет эквивалент EBO для членов.Пара лучше всего светится, когда используется в качестве возвращаемого типа функции вместе с деструктурированным присваиванием с использованием std :: tie и структурированной привязки C ++ 17. Использование std :: tie:
Используя структурированное связывание C ++ 17:
Плохой пример использования std :: pair (или tuple) будет выглядеть примерно так:
потому что при вызове std :: get <2> (player_data) неясно, что хранится в позиционном индексе 2. Помните, что читаемость, и для читателя должно быть понятно, что делает код, очень важен . Учтите, что это гораздо более читабельно:
В общем, вы должны думать о std :: pair и std :: tuple как о способах возврата более 1 объекта из функции. Эмпирическое правило, которое я использую (и видел, как многие другие используют его), заключается в том, что объекты, возвращаемые в std :: tuple или std :: pair, только «связаны» в контексте вызова функции, которая их возвращает. или в контексте структуры данных, которая связывает их вместе (например, std :: map использует std :: pair для своего типа хранения). Если отношения существуют где-то еще в вашем коде, вы должны использовать структуру.
Соответствующие разделы Основных руководящих принципов:
источник