Рекомендации по использованию файла заголовка для typedefs - PullRequest
51 голосов
/ 01 марта 2010

Я широко использую shared_ptr и STL в проекте, и это приводит к слишком длинным, подверженным ошибкам типам, таким как shared_ptr< vector< shared_ptr<const Foo> > > (я по предпочтению программист ObjC, где длинные имена являются нормой, и тем не менее, это слишком много.) Я думаю, было бы намного понятнее последовательно называть это FooListPtr и документировать соглашение об именах, что «Ptr» означает shared_ptr, а «List» означает вектор shared_ptr.

Это легко определить, но это вызывает головные боли с заголовками. Кажется, у меня есть несколько вариантов определения FooListPtr:

  • foo.h. Это переплетает все заголовки и создает серьезные проблемы со сборкой, поэтому это не стартер.
  • FooFwd.h («заголовок вперед»). Это то, что предлагает Effective C ++ на основе iosfwd.h. Это очень непротиворечиво, но затраты на поддержание удвоенного количества заголовков в лучшем случае раздражают.
  • Common.h (соберите их все в один файл). Это убивает возможность многократного использования, переплетая множество не связанных между собой типов. Теперь вы не можете просто взять один объект и переместить его в другой проект. Это не стартер.
  • Какая-то необычная магия #define, которая используется typedef, если она еще не определена. У меня непреодолимая неприязнь к препроцессору, потому что я думаю, что новым людям трудно уследить за кодом, но, возможно, ....
  • Используйте векторный подкласс, а не typedef. Это кажется опасным ...

Есть ли здесь лучшие практики? Как они получаются в реальном коде, когда многократное использование, читаемость и согласованность имеют первостепенное значение?

Я отметил эту вики сообщества, если другие хотят добавить дополнительные опции для обсуждения.

Ответы [ 5 ]

13 голосов
/ 27 октября 2010

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

Существует файл с именем ForwardsDecl.h, который находится в предварительно скомпилированном заголовке и просто объявляет все важные классы и необходимые определения типов. В этом случае unique_ptr используется вместо shared_ptr, но использование должно быть аналогичным. Это выглядит так:

// Forward declarations
class ObjectA;
class ObjectB;
class ObjectC;

// List typedefs
typedef std::vector<std::unique_ptr<ObjectA>> ObjectAList;
typedef std::vector<std::unique_ptr<ObjectB>> ObjectBList;
typedef std::vector<std::unique_ptr<ObjectC>> ObjectCList;

Этот код принят Visual C ++ 2010, хотя классы только объявлены заранее (полные определения классов не нужны, поэтому нет необходимости включать заголовочный файл каждого класса). Я не знаю, является ли это стандартным, и другим компиляторам потребуется полное определение класса, но полезно, что это не так: другой класс (ObjectD) может иметь ObjectAList в качестве члена, без необходимости включать ObjectA.h - это может действительно поможет уменьшить зависимости заголовочных файлов!

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

Наконец, кажется, что это может быть разделено между проектами (я сам не пробовал), потому что даже если проект фактически не объявляет ObjectA, это не имеет значения, потому что он был объявлен только форвардами, и если вы не используете это компилятор не волнует. Поэтому файл может содержать имена классов во всех проектах, в которых он используется, и не имеет значения, отсутствуют ли некоторые из них для определенного проекта. Все, что требуется, - это необходимый полный заголовок объявления (например, ObjectA.h), включенный в любые исходные (.cpp) файлы, которые фактически используют их.

6 голосов
/ 01 марта 2010

Я бы пошел с комбинированным подходом заголовков вперед и своего рода заголовком common.h, который специфичен для вашего проекта и включает в себя все заголовки прямого объявления и любые другие обычные и легкие вещи.

Вы жалуетесь на непроизводительные затраты на поддержание удвоенного количества заголовков, но я не думаю, что это должно быть слишком большой проблемой: передовые заголовки обычно должны знать только очень ограниченное количество типов (один?), И иногда даже не полный тип.

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

4 голосов
/ 01 марта 2010

+ 1 для документирования соглашений typedef.

  • Foo.h - не могли бы вы рассказать о проблемах, которые у вас возникли?
  • FooFwd.h - я бы вообще не использовал их, только на «очевидных горячих точках».(Да, «горячие точки» трудно определить ).Это не меняет правила IMO, потому что когда вы вводите заголовок fwd, связанные с ним typedefs из foo.h перемещаются туда.
  • Common.h - круто для небольших проектов, но неМасштаб, я согласен.
  • Некое причудливое # определение ... ПОЖАЛУЙСТА, НЕТ! ...
  • Использование векторного подкласса - не делает его лучше,Вы можете использовать сдерживание, однако.

Так что здесь предварительные предложения (пересмотрено с этого другого вопроса ..)

  1. Заголовки стандартного типа <boost/shared_ptr.hpp>, <vector> и т. Д. Могут входить в предварительно скомпилированный заголовок / общий файл включения для проекта Это неплохо. (лично я все еще включаю их там, где это необходимо, но это работает в дополнение к помещению их в PCH.)

  2. Если контейнер являетсяВ деталях реализации, typedefs идут туда, где объявлен контейнер (например, члены частного класса, если контейнер является членом частного класса)

  3. Связанные типы (например, FooListPtr) идут туда, где находится Fooобъявляется, , если , связанный тип является основным использованием этого типа.Это почти всегда верно для некоторых типов - например, shared_ptr.

  4. Если Foo получает отдельный заголовок предварительной декларации, и связанный с ним тип в порядке, он перемещается в FooFwd.h тоже.

  5. Если тип связан только с конкретным интерфейсом (например, параметр для открытого метода), он идет туда.

  6. Если тип является общим (и не соответствует ни одному из предыдущих критериев), он получает собственный заголовок.Обратите внимание, что это также означает использование всех зависимостей.

Мне кажется "очевидным", но я согласен, что это не очень хорошо в качестве стандарта кодирования.

2 голосов
/ 27 октября 2010

Я широко использую shared_ptr и STL в проекте, и это приводит к слишком длинным, подверженным ошибкам типам, таким как shared_ptr > (я по предпочтению программист ObjC, где длинные имена норма, и все же это слишком много. Я думаю, было бы гораздо понятнее последовательно вызывать этот FooListPtr и документировать соглашение об именах, что «Ptr» означает shared_ptr, а «List» означает вектор shared_ptr.

для начала, я рекомендую использовать хорошие структуры дизайна для определения области видимости (например, пространства имен), а также описательные, не сокращенные имена для typedefs. FooListPtr ужасно короткий, имо. никто не хочет догадываться, что означает сокращение (или удивляться, обнаружив, что Foo является const, shared и т. д.), и никто не хочет изменять свой код просто из-за коллизий в области видимости.

это также может помочь выбрать префикс для typedefs в ваших библиотеках (а также других распространенных категориях).

также плохая идея вытаскивать типы из заявленной области видимости:

namespace MON {
namespace Diddy {
class Foo;
} /* << Diddy */

/*...*/
typedef Diddy::Foo Diddy_Foo;

} /* << MON */

Есть исключения из этого:

  • полностью закрытый тип
  • отдельный тип в новой области видимости

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

Это легко определить, но это вызывает головные боли с заголовками. Кажется, у меня есть несколько вариантов определения FooListPtr:

foo.h. Это переплетает все заголовки и создает серьезные проблемы со сборкой, так что это не стартер.

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

FooFwd.h («прямой заголовок»). Это то, что предлагает Effective C ++ на основе iosfwd.h. Это очень непротиворечиво, но затраты на поддержание удвоенного количества заголовков в лучшем случае раздражают.

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

Common.h (соберите их все в один файл). Это убивает возможность многократного использования, переплетая множество не связанных между собой типов. Теперь вы не можете просто взять один объект и переместить его в другой проект. Это не стартер.

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

Какая-то необычная магия #define, которую использует typedef, если она еще не определена. У меня непреодолимая неприязнь к препроцессору, потому что я думаю, что новым людям трудно уследить за кодом, но, возможно, ....

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

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

, чтобы избежать этого, создайте «трансляцию сборки», которая включает в себя мир - компилятор будет отмечать объявления типов с неопределенными типами, которые не совпадают.

Попытка прокрасться с минимальными typedefs и / или forwards (которые достаточно близки для освобождения при компиляции) не стоит усилий. иногда вам понадобится куча условной поддержки для предварительных объявлений - как только это определено, это легко (библиотеки stl - хороший пример этого - если вы также объявите вперед template<typename,typename>class vector;).

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

Используйте векторный подкласс, а не typedef. Это кажется опасным ...

подкласс std::vector часто помечается как "ошибка новичка". этот контейнер не предназначен для использования в подклассах. не прибегайте к плохим практикам, просто чтобы сократить время компиляции / зависимости. если зависимость действительно настолько значительна, вы, вероятно, должны в любом случае использовать PIMPL:

// <package>.types.hpp
namespace MON {
class FooListPtr;
}

// FooListPtr.hpp
namespace MON {
class FooListPtr {
    /* ... */
private:
    shared_ptr< vector< shared_ptr<const Foo> > > d_data;
};
}

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

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

1 голос
/ 01 марта 2010

К сожалению, с typedefs вам приходится выбирать между не идеальными вариантами для ваших заголовочных файлов. Есть особые случаи, когда первый вариант (прямо в заголовке класса) работает хорошо, но, похоже, он вам не подойдет. Также есть случаи, когда последний вариант работает хорошо, но обычно вы используете подкласс для замены шаблона, включающего класс, одним членом типа std :: vector. В вашей ситуации я бы использовал решение для заголовка с предварительным объявлением. Есть дополнительная типизация и накладные расходы, но в противном случае это не будет C ++, верно? Это держит вещи отдельно, чисто и быстро.

...