Ответ 1
Это то, что было сделано Boost.Tuple.
Но вы, вероятно, должны теперь использовать std:: tuple...
Часто мне приходится использовать 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; }
};
Это лучше, так как мы можем получить доступ к переменным с помощью достаточно описательного имени. Проблема здесь в том, что вы все еще можете получить доступ первым и вторым, поэтому легко абстрагироваться от утечки. Кроме того, доступ к простым переменным через функции делает синтаксис раздражающим.
Очевидным следующим шагом является наследование private:
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, теперь у нас нет доступа к оператору < перегрузка, поскольку у нас нет доступа к первой и второй. Это означает, что мы должны определить функцию пересылки для каждой требуемой перегрузки оператора. Для меня это обычно ==,!= И <, но могут быть другие, которые я бы хотел. Да, я знаю, что я, вероятно, не должен перегружать оператора < просто придерживаться его в ассоциативном контейнере, но он делает все настолько простыми... И определение этих операторов для каждого нового типа является болью, и мы STILL должны получить доступ через функции. Мы можем исправить это:
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
Итак, теперь у нас есть простой доступ к переменной, но теперь определение перегруженных операторов еще больнее, потому что вместо того, чтобы просто перенаправлять их на реализацию пары, мы должны каждый раз их повторно выполнять...
Есть ли какие-либо решения, которые я упускал из виду, сделать это легко без недостатков? Если вы не хотите, чтобы вы предпочитали?
Это то, что было сделано Boost.Tuple.
Но вы, вероятно, должны теперь использовать std:: tuple...
Сотрудник указал мне на два возможных решения:
Использование повысить сильную typedef в качестве улучшенной версии typedef. Я никогда не слышал об этом раньше, и это, похоже, не является частью какой-либо суббиблиотеки, просто вроде плавающей.
Использование макроса для генерации кода, необходимого для разных операторов. Таким образом, мне не нужно было бы явно писать что-либо на уровне определения, просто сделайте что-то вроде DEFINE_PAIR_TYPE(Position, int, int, row, col);
. Это, вероятно, ближе всего к тому, что я ищу, но он по-прежнему чувствует себя злым по сравнению с некоторыми решениями, представленными другими.
Кроме того, библиотека Boost:: Operators автоматически генерирует код оператора. Это похоже на библиотеку SGI, которую предложил Martin York, но может быть более портативным.
Вы можете повторно использовать функциональность pair
, пересылая ей:
bool operator< ( const Position &a, const Position &b )
{
return
std::make_pair( a.row, a.col ) < std::make_pair( b.row, b.col );
}
Хотя вы все еще делаете это для каждой операции, вам нужно...
Вы можете использовать некоторые стандартные шаблоны утилиты, которые помогают определять операторы отношений.
#include <utility>
http://www.sgi.com/tech/stl/operators.html
Требование оператора!= состоит в том, что x == y является допустимым выражением
Требование операторa > состоит в том, что y < x - допустимое выражение
Требование оператора <= состоит в том, что y < x - допустимое выражение
Требование операторa >= состоит в том, что x < y является допустимым выражением
Таким образом, в основном он автоматически генерирует другие операторы, и == все, что вам нужно сделать, это включить <utility>
Я должен сказать, что много думал просто создать простую структуру.
Оператор перегрузки < и оператор ==, и все готово. Я использую это для большого количества кода, который пишу, главным образом потому, что обычно у меня больше переменных-членов, чем 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;
или поместите их внутри определения класса
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; };
Лучшие причины в том, что его легко понять выше, они легко могут быть легко удалены в определение класса, и их легко расширить, если вы обнаружите, что вам нужно больше переменных позже. Я бы никогда не попытался перегрузить std:: pair, когда было гораздо более простое решение.
Не используйте его.
Я ненавижу std:: pair именно по этой причине. Вы никогда не знаете, что есть, и поскольку доступ к первому и второму является общедоступным, вы также не можете выполнять контракты.
Но ведь это вопрос вкуса.
К сожалению strong typedef
s не попадет в С++ 0x, ему была дана классификация Неготова для С++ 0x, но открыта для повторной отправки в будущем.