Есть ли удобный способ обернуть std :: pair в новый тип? - PullRequest
13 голосов
/ 14 октября 2008

Часто я использую 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, у нас теперь нет доступа к перегрузке operator <, поскольку у нас нет доступа к first и second. Это означает, что мы должны определить функцию пересылки для каждой перегрузки оператора, которую мы хотим. Для меня это обычно ==,! = И <, но могут быть и другие, которые я бы хотел. Да, я знаю, что мне, вероятно, не следует перегружать оператор <, чтобы просто вставить его в ассоциативный контейнер, но это делает все чертовски простым ... И определение этих операторов для каждого нового типа является проблемой, и мы ДОЛЖНЫ иметь доступ через функции , Мы можем это исправить: </p>

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

Так что теперь у нас есть простой доступ к переменным, но теперь определение перегруженных операторов является еще более трудной задачей, потому что вместо того, чтобы просто перенаправлять их в реализацию пары, нам на самом деле приходится каждый раз заново их реализовывать ...

Есть ли какие-то решения, которые я упустил, чтобы сделать это легко без недостатков? Если нет, что бы вы предпочли?

Ответы [ 8 ]

6 голосов
/ 14 октября 2008

Это то, для чего Boost.Tuple был создан.

Но вы, вероятно, должны использовать std :: tuple сейчас ...

4 голосов
/ 14 октября 2008

Сотрудник указал мне на два возможных решения:

Использование boost strong typedef в качестве улучшенной версии typedef. Я никогда не слышал об этом раньше, и это, похоже, не является частью какой-либо суббиблиотеки, просто плавающее.

Использование макроса для генерации кода, необходимого для разных операторов. Таким образом, мне не нужно было бы явно что-то писать на уровне определения, просто сделать что-то вроде DEFINE_PAIR_TYPE(Position, int, int, row, col);. Это, вероятно, ближе всего к тому, что я ищу, но все равно ощущается как зло по сравнению с некоторыми решениями, представленными другими.

3 голосов
/ 15 октября 2008

Существует также библиотека Boost :: Operators для автоматической генерации кода оператора. Она похожа на библиотеку SGI, которую Мартин Йорк предложил , но может быть более переносимой.

2 голосов
/ 14 октября 2008

Вы можете использовать некоторые стандартные шаблоны утилит, которые помогают определить операторы отношений.

# включает

http://www.sgi.com/tech/stl/operators.html

Требования к типам

Требование к оператору! = Заключается в том, что x == y является допустимым выражением
Требование для оператора> состоит в том, что y Требование для оператора <= состоит в том, что y <x является допустимым выражением <br> Требование для оператора> = состоит в том, что x

Таким образом, в основном он будет автоматически генерировать другие операторы, которые дают <и == все, что вам нужно сделать, это включить <utility>

2 голосов
/ 14 октября 2008

Вы все еще можете использовать функциональность pair, перенаправив на нее:

bool operator< ( const Position &a, const Position &b ) 
{
    return
        std::make_pair( a.row, a.col ) < std::make_pair( b.row, b.col );
}

Хотя вам все равно придется делать это для каждой необходимой вам оперы ...

1 голос
/ 10 июня 2009

Не используйте его.

Я ненавижу std :: pair именно по этой причине. Вы никогда не знаете, что есть что, и так как доступ к первому и второму является публичным, вы также не можете применять контракты.

Но, в конце концов, дело вкуса.

1 голос
/ 16 октября 2008

Я должен сказать, что нужно много думать, чтобы создать простую структуру.

Оператор перегрузки <и оператор ==, и все готово. Я использую это для большого количества кода, который я пишу, главным образом потому, что у меня обычно есть больше переменных-членов для хранения, чем 2. </p>

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, когда есть гораздо более простое решение.

0 голосов
/ 28 октября 2008

К сожалению strong typedef s не превратится в C ++ 0x , ему присвоена классификация Не готов к C ++ 0x, но открыт для повторной отправки в будущем .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...