Soy un programador de C ++ con experiencia limitada.
Suponiendo que quiero usar un STL map
para almacenar y manipular algunos datos, me gustaría saber si hay alguna diferencia significativa (también en el rendimiento) entre esos 2 enfoques de estructura de datos:
Choice 1:
map<int, pair<string, bool> >
Choice 2:
struct Ente {
string name;
bool flag;
}
map<int, Ente>
Específicamente, ¿hay alguna sobrecarga usando un en struct
lugar de un simple pair
?
c++
data-structures
stl
Marco Stramezzi
fuente
fuente
std::pair
es una estructura.std::pair
es una plantilla .std::pair<string, bool>
es una estructurapair
está completamente desprovisto de semántica. Nadie que lea su código (incluido usted en el futuro) sabrá que esee.first
es el nombre de algo a menos que lo indique explícitamente. Soy un firme creyente de quepair
fue una adición muy pobre y perezosastd
, y que cuando se concibió nadie pensó "pero algún día, todos van a usar esto para todo lo que son dos cosas, y nadie sabrá lo que significa el código de nadie ".map
iteradores no sean excepciones válidas. ("first" = key y "second" = value ... realmentestd
,? Really?)Respuestas:
La opción 1 está bien para cosas pequeñas "usadas solo una vez". Esencialmente
std::pair
sigue siendo una estructura. Como se indica en este comentario, la opción 1 conducirá a un código realmente feo en algún lugar del agujero del conejo comothing.second->first.second->second
y nadie realmente quiere descifrar eso.La opción 2 es mejor para todo lo demás, porque es más fácil leer cuál es el significado de las cosas en el mapa. También es más flexible si desea cambiar los datos (por ejemplo, cuando Ente repentinamente necesita otra bandera). El rendimiento no debería ser un problema aquí.
fuente
Rendimiento :
Depende.
En su caso particular, no habrá diferencia de rendimiento porque los dos se presentarán de manera similar en la memoria.
En un caso muy específico (si estaba utilizando una estructura vacía como uno de los miembros de datos),
std::pair<>
podría utilizar la Optimización de base vacía (EBO) y tener un tamaño inferior al equivalente de la estructura. Y un tamaño más bajo generalmente significa un mayor rendimiento:Impresiones: 32, 32, 40, 40 en ideona .
Nota: No conozco ninguna implementación que realmente use el truco EBO para pares regulares, sin embargo, generalmente se usa para tuplas.
Legibilidad :
Además de las microoptimizaciones, una estructura con nombre es más ergonómica.
Quiero decir,
map[k].first
no es tan malo mientrasget<0>(map[k])
apenas es inteligible. Contrastar conmap[k].name
que inmediatamente indica de qué estamos leyendo.Es aún más importante cuando los tipos son convertibles entre sí, ya que intercambiarlos sin darse cuenta se convierte en una verdadera preocupación.
Es posible que también desee leer sobre Mecanografía estructural vs nominal.
Ente
es un tipo específico que sólo puede ser operado por las cosas que esperanEnte
, cualquier cosa que pueda operar sobrestd::pair<std::string, bool>
puede operar sobre ellos ... incluso cuando elstd::string
obool
no contiene lo que esperan, porquestd::pair
no tiene la semántica asociados a ella.Mantenimiento :
En términos de mantenimiento,
pair
es lo peor. No puedes agregar un campo.tuple
ferias mejor en ese sentido, siempre y cuando agregue el nuevo campo todos los campos existentes todavía se accede por el mismo índice. Lo cual es tan inescrutable como antes, pero al menos no necesita ir a actualizarlos.struct
Es el claro ganador. Puede agregar campos donde lo desee.En conclusión:
pair
es lo peor de ambos mundostuple
puede tener una ligera ventaja en un caso muy específico (tipo vacío),struct
.Nota: si usa getters, puede usar el truco de la base vacía usted mismo sin que los clientes tengan que saberlo como en
struct Thing: Empty { std::string name; }
; Es por eso que Encapsulación es el siguiente tema con el que debe preocuparse.fuente
first
ysecond
, no hay lugar para el vacío Base optimización a patada en.pair
que usaría EBO, pero un compilador podría proporcionar un incorporado. Sin embargo, dado que se supone que son miembros, puede ser observable (verificando sus direcciones, por ejemplo) en cuyo caso no sería conforme.[[no_unique_address]]
, lo que habilita el equivalente de EBO para los miembros.El par brilla más cuando se usa como el tipo de retorno de una función junto con la asignación desestructurada usando std :: tie y el enlace estructurado de C ++ 17. Usando std :: tie:
Usando el enlace estructurado de C ++ 17:
Un mal ejemplo de uso de un std :: pair (o tupla) sería algo como esto:
porque no está claro al llamar a std :: get <2> (player_data) lo que está almacenado en el índice de posición 2. Recuerde que la legibilidad y que sea obvio para el lector lo que hace el código es importante . Considere que esto es mucho más legible:
En general, debe pensar en std :: pair y std :: tuple como formas de devolver más de 1 objeto de una función. La regla general que uso (y he visto usar muchos otros también) es que los objetos devueltos en un par std :: tuple o std :: solo están "relacionados" dentro del contexto de hacer una llamada a una función que los devuelve o en el contexto de la estructura de datos que los une (por ejemplo, std :: map usa std :: pair para su tipo de almacenamiento). Si la relación existe en otra parte de su código, debe usar una estructura.
Secciones relacionadas de las Directrices básicas:
fuente