Подтвердить что ты не робот

Наилучшие примеры файлов заголовков для typedefs

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

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

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

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

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

4b9b3361

Ответ 1

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

Существует файл с именем ForwardsDecl.h, который находится в предварительно скомпилированном заголовке и просто пересылает все важные классы и необходимые typedef. В этом случае вместо shared_ptr используется unique_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 С++ 2010, даже если классы только декларируются вперед (определения полного класса не нужны, поэтому нет необходимости включать заголовочный файл каждого класса). Я не знаю, потребует ли этот стандарт и другие компиляторы полное определение класса, но полезно, что это не так: другой класс (ObjectD) может иметь ObjectAList в качестве члена, без необходимости включать ObjectA.h - это может действительно помогают уменьшить зависимости файлов заголовков!

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

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

Ответ 2

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

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

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

Ответ 3

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

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

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

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

  • Если контейнер представляет собой деталь реализации, то typedef отправляются туда, где объявлен контейнер (например, частные члены класса, если контейнер является частным членом класса).

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

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

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

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

Он чувствует себя "очевидным" для меня, но я согласен, что это не хорошо, как стандарт кодирования.

Ответ 4

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

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

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

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

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

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

} /* << MON */

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

  • полностью ecapsualted private type
  • содержащийся в новой области

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

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

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

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

FooFwd.h( "прямой заголовок" ). Это то, что предлагает эффективный С++, основанный на iosfwd.h. Это очень непротиворечиво, но накладные расходы на поддержание в два раза количества заголовков в лучшем случае раздражают.

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

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

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

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

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

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

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

пытается прокрасться с минимальными typedefs и/или форвардами (которые достаточно близки, чтобы освободить при компиляции) не стоит усилий. иногда вам понадобится куча условной поддержки для форвардных объявлений - как только это будет определено, это легко (библиотеки 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;
};
}

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

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

Ответ 5

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