Грамотное кодирование против. std::pair, решения?

Как и большинство программистов, я восхищаюсь и стараюсь следовать принципам Грамотного программирования, но в C++ я обычно использую std::pair для огромного количества общих задач. Но std::pair это, ИМХО, гнусный враг грамотного программирования...

Я хочу сказать, что когда я возвращаюсь к коду, который я написал день или два назад, и вижу манипуляции с std::pair (обычно в качестве итератора), я задаюсь вопросом: «что сделал iter->first и iter-> второе среднее???".

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


person Robert Gould    schedule 25.06.2009    source источник


Ответы (8)


Как насчет этого:

struct MyPair : public std::pair < int, std::string >
{
    const int& keyInt() { return first; }
    void keyInt( const int& keyInt ) { first = keyInt; }
    const std::string& valueString() { return second; }
    void valueString( const std::string& valueString ) { second = valueString; }
};

Это немного многословно, однако использование этого в вашем коде может упростить чтение, например:

std::vector < MyPair > listPairs;

std::vector < MyPair >::iterator iterPair( listPairs.begin() );
if ( iterPair->keyInt() == 123 )
    iterPair->valueString( "hello" );

Кроме этого, я не вижу никакой серебряной пули, которая прояснила бы ситуацию.

person Alan    schedule 25.06.2009
comment
хорошо, если не считать указания имен частей, таких как customerId() и customerName(), решение является жизнеспособным; просто нужно изменить keyInt с идентификатором клиента - person Robert Gould; 26.06.2009
comment
Я реализовал это, используя макрос для определения классов-оболочек. WRAPPER(wrapperName,baseName,firstName,secondName), поэтому я пишу WRAPPER(customer,_cutomerpair,id,name), чтобы я мог получить доступ к своим материалам как customer.id() и customer.name() также вместо getter/setter. Я просто используйте один key_type и firstName() - person Robert Gould; 26.06.2009

std::pair — это хороший способ создать «локальный» и по существу анонимный тип с по существу анонимными столбцами; если вы используете определенную пару в таком большом лексическом пространстве, что вам нужно назвать тип и столбцы, я бы вместо этого использовал простое struct.

person Alex Martelli    schedule 25.06.2009
comment
Я начал писать ответ, предлагающий классу-оболочке дать имя средствам доступа, но ваш намного проще. Если OP не нуждается в возможностях контейнера STL. - person DevSolar; 25.06.2009
comment
Это решение вполне разумно, но идея DevSolar была бы полезна при работе с std::map, подклассирование итератора может быть хорошим решением - person Robert Gould; 25.06.2009
comment
Для вариантов использования std::pair я обычно нахожу структуру слишком сложной. Простейшая структура (т. е. всего два члена) — это Aggregate, поэтому для нее требуется агрегатная инициализация или инициализация член за членом — для обоих из них требуется локальный временный объект. В качестве альтернативы вы можете добавить конструктор или написать эквивалент make_pair, оба из которых усложняют ситуацию IMHO. - person Richard Corden; 25.06.2009

typedef std::pair<bool, int> IsPresent_Value;
typedef std::pair<double, int> Price_Quantity;

... вы поняли.

person rlbond    schedule 25.06.2009
comment
Можете ли вы объяснить, как это помогает? Конечно, когда вы объявляете пару, это очевидно, но если вы находитесь в теле функтора, у которого в качестве параметра нет ничего, кроме value_type, вы понятия не имеете, что такое first и second. - person Richard Corden; 25.06.2009
comment
Современная IDE должна сообщать вам тип переменной при наведении на нее курсора. Или есть возможность перейти к его объявлению. - person rlbond; 25.06.2009

Вы можете создать две пары геттеров (const и non), которые будут просто возвращать ссылку на первый и второй, но будут гораздо более читабельными. Например:

string& GetField(pair& p) { return p.first; }
int& GetValue(pair& p) { return p.second; }

Позволит вам получить элементы поля и значения из заданной пары, не запоминая, какой элемент содержит что.

Если вы планируете использовать это часто, вы также можете создать макрос, который будет генерировать эти геттеры для вас, учитывая имена и типы: MAKE_PAIR_GETTERS(Field, string, Value, int) или около того. Упрощение геттеров, вероятно, позволит компилятору оптимизировать их, поэтому они не добавят накладных расходов во время выполнения; и использование макроса позволит легко создать эти геттеры для любого использования, которое вы используете для пар.

person eran    schedule 25.06.2009

Вы можете использовать кортежи boost, но они на самом деле не меняют основную проблему: действительно хотите ли вы получить доступ к каждой части пары/кортежа с помощью небольшого целочисленного типа, или вам нужен более «грамотный» код. См. этот вопрос, который я разместил некоторое время назад.

Однако boost::Optional это полезный инструмент, который, как я обнаружил, заменяет довольно много случаев, когда пары/кортежи рекламируются как ответ.

person Roddy    schedule 25.06.2009
comment
Кстати, как прямой результат вашего ответа и ссылки на вопрос, в настоящее время я изучаю, могу ли я легко перейти к использованию слияния, а не к моему текущему решению. - person Richard Corden; 25.06.2009
comment
Тоже хороший вопрос! Это в основном обобщенная версия моего вопроса :) - person Robert Gould; 26.06.2009

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

typedef boost::tuple<int, int> KeyValueTuple;
enum {
  KEY
  , VALUE
};

void foo (KeyValueTuple & p) {
    p.get<KEY> () = 0;
    p.get<VALUE> () = 0;
}

void bar (int key, int value)
{
  foo (boost:tie (key, value));
}

Кстати, комментарии приветствуются, если есть скрытые затраты на использование этого подхода.

EDIT: удалить имена из глобальной области.

Просто быстрый комментарий относительно глобального пространства имен. В общем, я бы использовал:

struct KeyValueTraits
{
  typedef boost::tuple<int, int> Type;
  enum {
    KEY
    , VALUE
  };
};

void foo (KeyValueTuple::Type & p) {
    p.get<KeyValueTuple::KEY> () = 0;
    p.get<KeyValueTuple::VALUE> () = 0;
}

Похоже, что boost::fusion действительно связывает личность и значение ближе друг к другу.

person Richard Corden    schedule 25.06.2009
comment
Ург. KEY и VALUE теперь находятся в глобальном пространстве имен. Хотя это, возможно, более читабельно, я не думаю, что это менее подвержено ошибкам. - person Roddy; 25.06.2009
comment
@Roddy: Ваша точка зрения о KEY, VALUE в глобальном пространстве имен верна, и я обновил свой ответ, чтобы отразить альтернативу. Однако можете ли вы объяснить, почему вы считаете, что это не менее подвержено ошибкам? - person Richard Corden; 25.06.2009
comment
@Richard: 0 и 1 довольно категоричны. p.get‹0›() = 0; некрасиво, но однозначно. Однако, если у меня есть (например) const int KEY = 1, за которым следует ваш фрагмент, я могу впоследствии написать p.get‹KEY›() = x; не понимая, что я использую неправильный КЛЮЧ. - person Roddy; 25.06.2009
comment
@Roddy: Можно создать множество примеров, когда сокрытие одного имени другим приводит к тому, что что-то идет не так. Тем не менее, я очень впечатлен boost::fusion, и как только я смогу понять, почему make_map не компилируется, я, вероятно, перенесу свои примеры кортежей, чтобы использовать карты слияния. - person Richard Corden; 25.06.2009
comment
Это хороший способ сделать кортеж более понятным для человека. Мне это нравится. - person Robert Gould; 26.06.2009

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

person stefanB    schedule 25.06.2009

Мне также не нравится std::pair в том виде, в котором он используется в std::map, в записях карты должны быть ключ и значение членов.
Я даже использовал boost::MIC, чтобы избежать этого. Тем не менее, boost::MIC также имеет свою цену.

Кроме того, возврат std::pair приводит к менее читаемому коду:

if (cntnr.insert(newEntry).second) { ... }

???

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

person stefaanv    schedule 25.06.2009