Часто я использую std :: pair для определения логических группировок двух связанных величин в качестве аргументов функции / возвращаемых значений. Некоторые примеры: строка / столбец, тег / значение и т. Д.
Часто мне действительно нужно использовать свой собственный класс, а не просто использовать std :: pair. Довольно легко увидеть, когда что-то начинает ломаться - когда код засоряется make_pair, во-первых, и во-вторых, очень трудно вспомнить, что есть что - std::pair<int, int>
передает меньше смысла, чем тип Position
.
Что вы нашли, это лучший способ обернуть функциональность std :: pair в тип, который передает реальный смысл?
Вот некоторые вещи, которые я рассмотрел:
typedef std::pair<int, int> Position;
Это, по крайней мере, дает типу осмысленное имя при его передаче, но тип не применяется принудительно, на самом деле это всего лишь пара, и большинство тех же проблем все еще существует. Однако это очень просто написать.
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; }
};
Это лучше, поскольку мы можем получить доступ к переменным через достаточно информативное имя. Проблема здесь в том, что вы все еще можете получить доступ к первому и второму, поэтому утечка абстракции не вызывает затруднений. Кроме того, доступ к простым переменным через функции раздражает синтаксис.
Следующий очевидный шаг - сделать наследование закрытым:
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...
};
Итак, теперь, по крайней мере, мы избавились от доступа к первому и второму, но теперь возникает новая проблема. Когда мы хотим сохранить тип в std :: set, у нас теперь нет доступа к перегрузке оператора ‹, поскольку у нас нет доступа к first и second. Это означает, что мы должны определить функцию пересылки для каждой перегрузки оператора, которую мы хотим. Для меня это обычно ==,! = И ‹, но могут быть и другие, которые мне нужны. Да, я знаю, что мне, вероятно, не следует перегружать оператор ‹просто для того, чтобы вставить его в ассоциативный контейнер, но это делает все чертовски простым ... И определение этих операторов для каждого нового типа - это боль, и мы ВСЕ ЕЩЕ должны получить доступ через функции . Мы можем это исправить:
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
Итак, теперь у нас есть простой доступ к переменным, но теперь определение перегруженных операторов становится еще более сложной задачей, потому что вместо того, чтобы просто пересылать их реализации пары, мы фактически должны каждый раз заново реализовывать их ...
Есть ли какие-то решения, которые я упустил из виду, которые упрощают задачу без недостатков? Если нет, что бы вы предпочли?