c++ - geeksforgeeks - stl map
¿Hay una forma conveniente de ajustar std:: pair como un nuevo tipo? (8)
Muchas veces me encuentro usando std :: pair para definir agrupaciones lógicas de dos cantidades relacionadas como argumentos de función / valores de retorno. Algunos ejemplos: row / col, tag / value, etc.
Muchas veces realmente debería lanzar mi propia clase en lugar de simplemente usar std :: pair. Es bastante fácil ver cuándo las cosas empiezan a descomponerse: cuando el código se llena de make_pair, primero y segundo, es muy difícil recordar qué es qué, un std::pair<int, int>
transmite menos significado que un tipo Position
.
¿Qué ha encontrado son las mejores formas de ajustar la funcionalidad de std :: pair en un tipo que transmite un significado real?
Aquí hay algunas cosas que he considerado:
typedef std::pair<int, int> Position;
Esto al menos le da al tipo un nombre significativo al pasarlo, pero el tipo no se aplica, todavía es solo un par, y la mayoría de los mismos problemas aún existen. Sin embargo, esto es muy simple de escribir.
struct Position : public std::pair<int, int>
{
typedef std::pair<int, int> Base;
Position() : Base() {}
Position(const Position &x) : Base(x) {}
Position(int a, int b) : Base(a, b) {}
int &row() { return first; }
const int &row() const { return first; }
int &col() { return second; }
const int &col() const { return second; }
};
Esto es mejor, ya que podemos acceder a las variables a través de un nombre razonablemente descriptivo. El problema aquí es que todavía se puede acceder primero y segundo, por lo que es fácil que la abstracción se filtre. Además, acceder a variables simples a través de funciones hace que la sintaxis sea molesta.
El siguiente paso obvio es hacer que la herencia sea privada:
struct Position : private std::pair<int, int>
{
typedef std::pair<int, int> Base;
Position() {}
Position(const Position &x) : Base(x) {}
Position(int a, int b) : Base(a, b) {}
int &row() { return first; }
const int &row() const { return first; }
int &col() { return second; }
const int &col() const { return second; }
bool operator<(const Position &x) const { return Base(*this) < Base(x); }
// other forwarding operators as needed...
};
Así que ahora, al menos, hemos eliminado el acceso al primero y al segundo, pero ahora aparece un nuevo problema. Cuando queremos almacenar el tipo en un std :: set, ahora no tenemos acceso a la sobrecarga del operador <, ya que no tenemos acceso al primero y al segundo. Esto significa que debemos definir una función de reenvío para cada sobrecarga de operador que deseemos. Para mí esto suele ser ==,! = Y <, pero podría haber otros que quisiera. Sí, sé que probablemente no debería sobrecargar al operador <solo para pegarlo en un contenedor asociativo, pero hace que todo sea tan simple ... Definir estos operadores para cada nuevo tipo es un problema, y TODAVÍA tenemos que acceder a través de funciones . Podemos arreglarlo:
struct Position
{
Position() {}
Position(const Position &x) : row(x.row), col(x.col) {}
Position(int row, int col) : row(row), col(col) {}
int row, col;
};
bool operator<(const Position &a, const Position &b)
{
return a.row < b.row || (!(b.row < a.row) && a.col < b.col);
}
// more overloads as needed
Así que ahora tenemos acceso variable simple, pero ahora definir operadores sobrecargados es aún más doloroso, porque en lugar de simplemente reenviarlos a la implementación del par, en realidad tenemos que volver a implementarlos cada vez ...
¿Hay alguna solución que haya pasado por alto que lo haga fácil sin los inconvenientes? Si no hay, ¿cuál tenderías a preferir?
Puede utilizar algunas plantillas de utilidad estándar que ayudan a definir los operadores de relación.
#include <utilidad>
http://www.sgi.com/tech/stl/operators.html
Requisitos sobre tipos
El requisito para el operador! = Es que x == y es una expresión válida
El requisito para el operador> es que y <x es una expresión válida
El requisito para el operador <= es que y <x es una expresión válida
El requisito para operador> = es que x <y es una expresión válida
Así que, básicamente, generará automáticamente los otros operadores dar <y ==, todo lo que tienes que hacer es incluir <utilidad>
Todavía puede reutilizar la funcionalidad de pair
reenviando a ella:
bool operator< ( const Position &a, const Position &b )
{
return
std::make_pair( a.row, a.col ) < std::make_pair( b.row, b.col );
}
Aunque todavía terminas haciendo esto para cada operación que necesites ...
Un compañero de trabajo me señaló dos posibles soluciones:
Usando boost strong typedef como una versión mejorada de typedef. Nunca había oído hablar de esto antes, y no parece ser realmente parte de una sub biblioteca, simplemente flotando.
Usando una macro para generar el código necesario para los diferentes operadores. De esta forma no tendría que escribir explícitamente nada en un nivel por definición, simplemente haga algo como DEFINE_PAIR_TYPE(Position, int, int, row, col);
. Esto es probablemente lo más cercano a lo que estoy buscando, pero todavía se siente mal en comparación con algunas de las soluciones presentadas por otros.
Debo decir que eso es una gran cantidad de pensamiento solo para hacer una estructura simple.
Operador de sobrecarga <y operador == y listo. Lo uso para una gran cantidad de código que escribo, principalmente porque generalmente tengo más variables miembro para almacenar que 2.
struct MyStruct
{
std::string var1;
std::string var2;
bool var3;
struct less : std::binary_function<struct MyStruct, struct MyStruct, bool>
{
bool operator() (const struct MyStruct& s1, const struct MyStruct& s2) const
{ if (var1== a2.var1) return var2 < a2.var2; else return var3 < a2.var3; }
};
};
typedef std::set<struct MyStruct, MyStruct::less> MySet;
o ponerlos dentro de la definición de la clase
bool operator==(const MyStruct& rhs) const
{ return var1 == rhs.var1 && var2 == rhs.var2 && var3 == rhs.var3; };
bool operator<(const MyStruct& a2) const
{ if (var1== a2.var1) return var2 < a2.var2; else return var3 < a2.var3; };
Las mejores razones son que es fácil entender lo anterior, se pueden deslizar en la definición de clase fácilmente, y son fáciles de expandir si encuentra que necesita más variables más adelante. Nunca trataría de sobrecargar std :: pair cuando hay una solución mucho más simple.
Desafortunadamente, los typedef
fuertes no llegarán a C ++ 0x , se le ha dado la clasificación de No preparado para C ++ 0x, pero está abierto para volver a enviarse en el futuro .
No lo uses
Odio std :: pair exactamente por esta razón. Nunca se sabe cuál es cuál, y como el acceso al primero y al segundo es público, tampoco se puede hacer cumplir los contratos.
Pero después de todo, es una cuestión de gusto.
Esto es para lo que se creó Boost.Tuple .
Pero probablemente deberías estar usando std :: tuple ahora ...
También está la biblioteca Boost :: Operators para generar automáticamente código de operador. Es similar a la biblioteca SGI que sugirió Martin York , pero podría ser más portátil.