Внутренние определения типов в C ++ - хороший или плохой стиль?

Что-то, что я часто делал в последнее время, - это объявление typedef, относящихся к определенному классу внутри этого класса, т.е.

class Lorem
{
    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
};

Эти типы затем используются в другом месте кода:

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

Причины, по которым мне это нравится:

  • Это уменьшает шум, создаваемый шаблонами классов, std::vector<Lorem> становится Lorem::vector и т. Д.
  • Он служит заявлением о намерениях - в приведенном выше примере класс Lorem предназначен для подсчета ссылок через boost::shared_ptr и сохранения в векторе.
  • Это позволяет изменить реализацию - то есть, если Lorem нужно будет изменить для навязчивого подсчета ссылок (через boost::intrusive_ptr) на более позднем этапе, то это окажет минимальное влияние на код.
  • Я думаю, что он выглядит «красивее» и, возможно, его легче читать.

Причины, по которым мне это не нравится:

  • Иногда возникают проблемы с зависимостями - если вы хотите встроить, скажем, Lorem::vector в другой класс, но вам нужно (или вы хотите) только переслать объявление Lorem (в отличие от введения зависимости от его файла заголовка), тогда вам придется использовать явные типы (например, boost::shared_ptr<Lorem>, а не Lorem::ptr), что немного противоречиво.
  • Может быть, это не очень распространено и, следовательно, труднее понять?

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


person Will Baker    schedule 17.04.2009    source источник


Ответы (9)


Я считаю, что это отличный стиль, и сам им пользуюсь. Всегда лучше ограничивать область имен, насколько это возможно, и использование классов - лучший способ сделать это в C ++. Например, стандартная библиотека C ++ интенсивно использует определения типов внутри классов.

person Community    schedule 17.04.2009
comment
Это хороший момент, мне интересно, что это выглядело «красивее», когда мое подсознание деликатно указывало на то, что ограниченный объем - это хорошо. Интересно, однако, делает ли тот факт, что STL использует его преимущественно в шаблонах классов, несколько иное использование? В «конкретном» классе сложнее оправдать? - person Will Baker; 17.04.2009
comment
Стандартная библиотека состоит из шаблонов, а не классов, но я думаю, что обоснование одинаково для обоих. - person ; 17.04.2009

Он служит заявлением о намерениях - в приведенном выше примере класс Lorem предназначен для подсчета ссылок с помощью boost :: shared_ptr и сохранения в векторе.

Это именно то, чего он не.

Если я вижу в коде «Foo :: Ptr», я совершенно не понимаю, является ли это shared_ptr или Foo * (в STL есть определения типов :: pointer typedef, которые являются T *, помните) или что-то еще. Esp. если это общий указатель, я вообще не предоставляю typedef, но сохраняю использование shared_ptr явно в коде.

На самом деле, я почти никогда не использую typedef вне метапрограммирования шаблонов.

STL делает такие вещи постоянно

Дизайн STL с концепциями, определенными в терминах функций-членов и вложенных определений типов, является историческим тупиком, современные библиотеки шаблонов используют бесплатные функции и классы свойств (см. Boost.Graph), поскольку они не исключают встроенные типы из моделирование концепции и потому, что это упрощает адаптацию типов, которые не были разработаны с учетом концепций заданных библиотек шаблонов.

Не используйте STL как повод для тех же ошибок.

person Marc Mutz - mmutz    schedule 05.01.2010
comment
Я согласен с вашей первой частью, но ваше недавнее изменение немного недальновидно. Такие вложенные типы упрощают определение классов признаков, поскольку они обеспечивают разумное значение по умолчанию. Рассмотрим новый класс std::allocator_traits<Alloc> ... вам не нужно специализировать его для каждого отдельного распределителя, который вы пишете, потому что он просто заимствует типы непосредственно из Alloc. - person Dennis Zickefoose; 11.11.2011
comment
@Dennis: В C ++ удобство должно быть на стороне / user / библиотеки, а не ее / author /: пользователь желает единообразного интерфейса для признака, и только класс признака может дать это, по причинам, указанным выше). Но даже для Alloc автора не совсем сложнее специализировать std::allocator_traits<> для своего нового типа, чем добавить необходимые определения типов. Я также отредактировал ответ, потому что мой полный ответ не вписался в комментарий. - person Marc Mutz - mmutz; 12.11.2011
comment
Но это на стороне пользователя. Как пользователь allocator_traits, пытающийся создать собственный распределитель, мне не нужно беспокоиться о пятнадцати элементах класса признаков ... все, что мне нужно сделать, это сказать typedef Blah value_type; и предоставить соответствующий член функции, а значение по умолчанию allocator_traits сделает все остальное. Далее посмотрите на ваш пример Boost.Graph. Да, он интенсивно использует класс признаков ... но реализация graph_traits<G> по умолчанию просто запрашивает G свои собственные внутренние определения типов. - person Dennis Zickefoose; 12.11.2011
comment
И даже стандартная библиотека 03 использует классы признаков там, где это необходимо ... философия библиотеки заключается не в том, чтобы работать с контейнерами в целом, а в работе с итераторами. Таким образом, он предоставляет класс iterator_traits, чтобы ваши общие алгоритмы могли легко запрашивать соответствующую информацию. Что, опять же, по умолчанию запрашивает у итератора собственную информацию. Короче говоря, черты и внутренние определения типов вряд ли являются взаимоисключающими ... они поддерживают друг друга. - person Dennis Zickefoose; 12.11.2011
comment
@Dennis: iterator_traits стало необходимым, потому что T* должен быть моделью RandomAccessIterator, но вы не можете поместить требуемые typedef в T*. Как только у нас появился iterator_traits, вложенные определения типов стали излишними, и я бы хотел, чтобы их тут же удалили. По той же причине (невозможность добавить внутренние определения типов) T[N] не моделирует концепцию STL Sequence, и вам нужны кладжи, такие как std::array<T,N>. Boost.Range показывает, как можно определить современную концепцию Sequence, которую T[N] может моделировать, поскольку она не требует вложенных определений типов или функций-членов. - person Marc Mutz - mmutz; 14.11.2011
comment
boost.org/doc/libs/1_35_0/ libs / range / doc / Ниже дается обзор того, какие функции-члены и типы членов должен указать класс, чтобы их можно было использовать в качестве определенной концепции диапазона. [курсив мой] - person Dennis Zickefoose; 14.11.2011
comment
Дело в том, что Boost делает именно то, что я сказал, используя вложенные типы, чтобы упростить использование трейтов. Зачем вообще поддерживать метод 1, если вложенные типы устарели и бесполезны? - person Dennis Zickefoose; 15.11.2011

Typedef - это то, на чем основанный на политике дизайн и черты построены в C ++, поэтому сила универсального программирования в C ++ проистекает из самих определений типов.

person Özgür    schedule 17.04.2009

Typdef - это определенно хороший стиль. И все ваши «причины, которые мне нравятся» хороши и правильны.

О проблемах, которые у вас есть с этим. Что ж, предварительное заявление - это не Святой Грааль. Вы можете просто разработать свой код, чтобы избежать многоуровневых зависимостей.

Вы можете переместить typedef за пределы класса, но Class :: ptr настолько красивее, чем ClassPtr, что я этого не делаю. Это похоже на пространства имен, как для меня - вещи остаются связанными в пределах области видимости.

Иногда я делал

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

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

person Mykola Golubyev    schedule 17.04.2009

STL делает такие вещи все время - определения типов являются частью интерфейса для многих классов в STL.

reference
iterator
size_type
value_type
etc...

все это typedef, которые являются частью интерфейса для различных классов шаблонов STL.

person Michael Burr    schedule 17.04.2009
comment
Верно, и я подозреваю, что именно здесь я впервые его подобрал. Кажется, это было бы немного легче оправдать? Я не могу не рассматривать typedef в шаблоне класса как более похожий на переменные, если вы думаете о «метапрограммировании». - person Will Baker; 17.04.2009

Еще одно голосование за то, что это хорошая идея. Я начал это делать, когда писал симуляцию, которая должна была быть эффективной как во времени, так и в пространстве. Все типы значений имели Ptr typedef, который начинался как общий указатель boost. Затем я выполнил некоторое профилирование и изменил некоторые из них на навязчивый указатель boost без необходимости изменять какой-либо код, в котором использовались эти объекты.

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

person KeithB    schedule 17.04.2009

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

Но я заметил, что довольно часто встречаются итеративные определения типов, определения разделены между несколькими классами, и вы никогда не знаете, с каким типом имеете дело. Моя задача - суммировать размер некоторых сложных структур данных, скрытых за этими определениями типов, поэтому я не могу полагаться на существующие интерфейсы. В сочетании с тремя-шестью уровнями вложенных пространств имен, это сбивает с толку.

Поэтому перед их использованием следует учесть несколько моментов.

  • Кому-нибудь еще нужны эти typedef? Часто ли этот класс используется другими классами?
  • Могу ли я сократить использование или скрыть класс? (В случае сокрытия вы также можете подумать об интерфейсах.)
  • С кодом работают другие люди? Как они это делают? Они подумают, что это проще, или запутаются?
person Bodo    schedule 18.01.2014

Когда typedef используется только внутри самого класса (т.е. объявлен как закрытый), я думаю, что это хорошая идея. Однако по точно указанным вами причинам я бы не стал использовать его, если typedef необходимо знать за пределами класса. В таком случае рекомендую вынести их за пределы класса.

person Stefan Rådström    schedule 17.04.2009

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

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

person Cătălin Pitiș    schedule 17.04.2009
comment
Это могло бы засорять анонимное пространство имен с помощью typedef, не так ли ?! Проблема с typedef заключается в том, что он скрывает фактический тип, что может вызвать конфликты при включении в / несколькими модулями, которые трудно найти / исправить. Рекомендуется содержать их в пространствах имен или внутри классов. - person Indy9000; 17.04.2009
comment
Конфликты имен и анонимное загрязнение пространства имен имеют мало общего с сохранением имени типа внутри класса или вне его. У вас может возникнуть конфликт имени с вашим классом, а не с вашими определениями типов. Поэтому, чтобы избежать загрязнения имен, используйте пространства имен. Объявите свой класс и связанные с ним определения типов в пространстве имен. - person Cătălin Pitiș; 17.04.2009
comment
Еще один аргумент в пользу помещения typedef внутри класса - использование шаблонных функций. Когда, скажем, функция получает неизвестный тип контейнера (вектор или список), содержащий неизвестный строковый тип (строка или ваш собственный строковый вариант). единственный способ выяснить тип полезной нагрузки контейнера - использовать typedef 'value_type', который является частью определения класса контейнера. - person Marius; 23.12.2009