2010-03-01 4 views
51

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

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

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

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

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

+5

Могу ли я спросить, почему этот вопрос является вики-сообществом? –

+0

@ Konrad, если бы были другие предложения, я предлагал добавить их в список, чтобы упростить для последующих читателей различные варианты, отличные от ответов по их достоинствам. Возможно, вики сообщества используются по-разному? –

+1

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

ответ

6

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

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

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

+0

В основном я работаю над кодом, который используется несколькими проектами, поэтому почти ни один из них не относится к одному проекту. Единственный «common.h» очень затрудняет повторное использование компонентов несколькими проектами. –

+0

@ Konrad: использует ли SeqAn библиотеки boost и std? Что делает сценарий, который вы упомянули? –

+0

@ Benoît: SeqAn использует STL, но (к сожалению, IMHO) нет библиотек Boost. Что касается скрипта, он просто анализирует все файлы заголовков (но тогда SeqAn только заголовки) и генерирует один большой файл, содержащий передовые объявления всех экспортируемых типов и функций. –

1

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

+0

Это, кажется, самый последовательный и масштабируемый подход. «это не будет C++ иначе ...» Кажется, это так. Обычно я программист ObjC. Люди жалуются на длинные имена в ObjC, но я нахожу их очень удобными и не против их вообще. Но в C++ я чувствую, что потратил половину своего времени на сложную занятую работу, или же сделать код непоследовательным и трудноподдерживаемым, чтобы избежать лишнего ввода текста. –

4

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

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

Так вот в prelimenary предложений (пересмотрено с этим другим вопросом ..)

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

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

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

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

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

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

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

+0

хорошие точки в целом, но re # 1: я не рекомендую включать множество стандартных библиотек в общий заголовок. самым худшим нарушением является количество созданных частных статических данных и определений. – justin

+0

@ Justin - Определения - согласованы, но для этого нужны прекомпилированные заголовки. но частные статические данные? в стандартных заголовках почти нет ни одного (как и для каждой единицы перевода). Или вы имеете в виду проблемы с ранними версиями поддержки шаблонов? Если это не известная и проверенная проблема для вашего компилятора, нет смысла придерживаться ее. – peterchen

+0

Я не использую предварительно скомпилированные заголовки. Обоснование: недостаточно общности, и я часто использую комбинированные сборки, поэтому во многих случаях один файл скомпилирован для каждого пакета. Кроме того, предварительно скомпилированные заголовки не очень хорошо масштабируются в распределенных системах сборки. что касается статических данных, одно очевидное объявление, которое появляется, это потоки ввода/вывода (включены в «iostream»). я потерял около 20% некоторых двоичных файлов * после удаления лишних включений из стандартных библиотек (измеренных с использованием относительно современной реализации std libs, поставляемых с gcc apple). проблемы, очевидно, выходят за рамки двоичного размера. – justin

13

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

Существует файл с именем ForwardsDecl.h, который находится в предварительно скомпилированном заголовке и просто пересылает все важные классы и необходимые typedefs. В этом случае 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), который фактически использует.

+0

Это очень похоже на то, как я организовал свои библиотеки на C++. Точка заголовочного файла forward declarations заключается в том, что он должен объявлять все классы и типы указателей внутри библиотеки. Таким образом, вам нужен только один такой заголовок, а не один класс. Подробнее см. Http://stackoverflow.com/questions/3935183/forward-declaration-include-on-top-of-declaration-include-classfwd-h-class-h/3935468#3935468. –

2

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

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

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

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

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

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

} /* << MON */ 

есть исключения к этому:

  • полностью ecapsualted частного типа
  • содержимого типа в пределах новый объем

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

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

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

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

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

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

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

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

Какая-то причудливая маска #define, которая 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; 
}; 
} 

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

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

Смежные вопросы