Многие объяснения уже присутствуют, чтобы объяснить, как это происходит и как это исправить, но вы также должны следовать рекомендациям, чтобы избежать NullPointerException
вообще.
См. также: A хороший список лучших практик
Я бы добавил, очень важно, хорошо использовать модификатор final
. Использование "окончательной" модификатор, когда это применимо в Java
Сводка:
final
для обеспечения хорошей инициализации. @NotNull
и @Nullable
if("knownObject".equals(unknownObject)
valueOf()
поверх toString (). StringUtils
StringUtils.isEmpty(null)
. Не в моих проектах: исходные файлы (CPP) только включают заголовки (HPP), им нужно. Таким образом, когда я должен перекомпилировать только один CPP из-за крошечного изменения, у меня есть десять раз то же количество файлов, которые не перекомпилированы.
, Возможно, необходимо сломать проект в более логических источниках/заголовках: для модификации в реализации класса A не должна быть нужна перекомпиляция реализаций класса B, C, D, E, и т.д.
Круговые зависимости в коде?
Извините, но у меня должен все же быть этот вид проблемы, являющейся настоящей проблемой: Скажем, A зависит от B, и B зависит от A:
struct A
{
B * b ;
void doSomethingWithB() ;
} ;
struct B
{
A * a ;
void doSomethingWithA() ;
} ;
void A::doSomethingWithB() { /* etc. */ }
void B::doSomethingWithA() { /* etc. */ }
А хороший способ разрешить проблему состоял бы в том, чтобы сломать этот источник по крайней мере в один источник/заголовок в классе (в некотором роде подобный Java путь, но с одним источником и одним заголовком в классе):
// A.hpp
struct B ;
struct A
{
B * b ;
void doSomethingWithB() ;
} ;
.
// B.hpp
struct A ;
struct B
{
A * a ;
void doSomethingWithA() ;
} ;
.
// A.cpp
#include "A.hpp"
#include "B.hpp"
void A::doSomethingWithB() { /* etc. */ }
.
// B.cpp
#include "B.hpp"
#include "A.hpp"
void B::doSomethingWithA() { /* etc. */ }
Таким образом, без проблем зависимости, и все еще быстрое время компиляции.
я пропускал что-то?
в реальном проекте, cpp файлы имеют тенденцию включать случайные заголовки, пока Вы не можете выяснить, кто зависит от кого
, Конечно. Но тогда если у Вас есть время для реорганизации тех файлов для создания "одного CPP" решение, тогда у Вас есть время для очистки тех заголовков. Мои правила для заголовков:
Так или иначе, все заголовки должны быть автономной системой, что означает:
, Это удалит проблемы упорядочивания и круговые зависимости.
Должен время компиляции быть действительно проблемой, я рассмотрел бы также:
, Что Вы делаете, не помещает все в заголовки.
Вы в основном включаете все свои файлы в один и только один заключительный источник.
, Возможно, Вы побеждаете с точки зрения полной компиляции проекта.
, Но при компиляции для одного небольшого изменения, Вы будете всегда проигрывать.
При кодировании, я знаю, что компилирую часто небольшие изменения (если только иметь компилятор, проверяют мой код), и затем одно заключительное время, сделайте полное изменение проекта.
я потерял бы много времени, если бы мой проект был организован Ваш путь.
При использовании шаблонных классов необходимо поместить целую реализацию в заголовок так или иначе...
Компиляция целого проекта сразу (через единственную основу .cpp файл) должна позволить что-то как "Целая Оптимизация Программы" или "Оптимизация перекрестного Модуля", которая только доступна в нескольких усовершенствованных компиляторах. Это не действительно возможно со стандартным компилятором, если Вы предварительно компилируете все свои .cpp файлы в объектные файлы, затем связываясь.
Одна вещь, которую никто не поднял, состоит в том, что компиляция больших файлов требует партия из памяти. Компиляция Вашего целого проекта сразу потребовала бы такого огромного пространства памяти, что просто не выполнимо, даже если Вы могли бы поместить весь код в заголовки.
еще менее прозрачные клуджи static-or-global-variable, возможно, un-debuggable.
, например, подсчет общего количества повторений для анализа.
В МОИХ kludged файлах, помещая такие объекты наверху cpp файла делает их легкими найти.
, "возможно, un-debuggable", я подразумеваю, что обычно буду помещать такое глобальное в окно WATCH. Так как это всегда в объеме - окно WATCH, может всегда добираться до него, неважно, где счетчик команд, оказывается, прямо сейчас. Путем помещения таких переменных вне {} наверху заголовочного файла Вы позволили бы всему нисходящему коду "видеть" их. Путем вставления их {}, бесцеремонно я думал бы, что отладчик больше не будет рассматривать их "в объеме", если счетчик команд будет вне {}. Принимая во внимание, что с kludge-global-at-Cpp-top, даже при том, что это могло бы быть глобально вплоть до разоблачения в Вашем link-map-pdb-etc без оператора экстерна, который другие файлы Cpp не могут получить к нему, избежав случайной связи.
Ну, поскольку многие указали, там много недостатков к этой идее, но балансировать немного и обеспечить про, я сказал бы, что наличие некоторого кода библиотеки полностью в заголовках имеет смысл, так как это сделает его независимым от других настроек в проекте, в котором это используется.
, Например, при попытке использовать различные библиотеки Open Source, они могут быть установлены использовать разные подходы для соединения с Вашей программой - некоторые могут использовать динамично загруженный код библиотеки операционной системы, другие установлен быть статически связанным; некоторые могут быть установлены использовать многопоточность, в то время как другие не. И это может быть подавляющая задача для программиста - особенно с ограничением времени - чтобы попытаться уладить эти несовместимые approches.
Все это является однако не проблемой при пользовании библиотеками, которые содержатся полностью в заголовках. "Это просто работает" на разумную правильно написанную библиотеку.
Одна проблема с кодом в заголовках состоит в том, что он должен быть встроен, иначе у Вас будут проблемы повторного определения при соединении нескольких единиц перевода, которые включают тот же самый заголовок.
исходный вопрос определил, что был только когда-либо единственный cpp в проекте, но дело не в этом при создании компонента, предназначенного для допускающей повторное использование библиотеки.
Поэтому в интересах создания самого допускающего повторное использование и удобного в сопровождении кода как возможный, только помещенный встроенный и inlinable код в заголовочных файлах.
Мне нравится думать о разделении.h и .cpp файлов с точки зрения интерфейсов и реализаций..h файлы содержат описания интерфейса к еще одному, классы и .cpp файлы содержат реализации. Иногда существуют практические проблемы или ясность, которые предотвращают абсолютно чистое разделение, но это - то, где я запускаю. Например, маленькое средство доступа функционирует, я обычно кодирую встроенный в объявлении класса для ясности. Большие функции кодируются в .cpp файле
В любом случае, не позволяйте времени компиляции продиктовать, как Вы структурируете свою программу. Лучше для имения программы это читаемо и удобно в сопровождении по тому, которое компилирует за 1,5 минуты вместо 2 минут.
Я полагаю, что, если Вы не используете предварительно скомпилированные заголовки MSVC, и Вы используете Make-файл, или другая основанная на зависимости система сборки, имея отдельные исходные файлы должна скомпилировать быстрее при создании многократно. С тех пор моя разработка является почти всегда повторяющейся, я забочусь больше о том, как быстро она может перекомпилировать изменения, которые я внес в файле x.cpp, чем в двадцати других исходных файлах, которые я не изменил. Кроме того, я вношу изменения намного более часто в исходные файлы, чем я делаю к API, таким образом, они изменяются менее часто.
Относительно, круговые зависимости. Я послушал бы совет paercebal один шаг дальше. У него было два класса, которые имели указатели друг на друга. Вместо этого я сталкиваюсь со случаем более часто, где один класс требует другого класса. Когда это происходит, я включаю заголовочный файл для зависимости в заголовочном файле другого класса. Пример:
// foo.hpp
#ifndef __FOO_HPP__
#define __FOO_HPP__
struct foo
{
int data ;
} ;
#endif // __FOO_HPP__
.
// bar.hpp
#ifndef __BAR_HPP__
#define __BAR_HPP__
#include "foo.hpp"
struct bar
{
foo f ;
void doSomethingWithFoo() ;
} ;
#endif // __BAR_HPP__
.
// bar.cpp
#include "bar.hpp"
void bar::doSomethingWithFoo()
{
// Initialize f
f.data = 0;
// etc.
}
причина, что я включаю это, которое немного не связано с круговыми зависимостями, состоит в том, что я чувствую, что существуют альтернативы включению заголовочных файлов волей-неволей. В этом примере исходный файл панели структуры не включает заголовочный файл нечто структуры. Это сделано в заголовочном файле. Это имеет преимущество в этом, разработчик, использующий панель, не должен знать ни о каких других файлах, что разработчик должен был бы включать для использования того заголовочного файла.
Вы выходите на улицу из объема дизайна языка. В то время как можно обладать некоторыми преимуществами, это собирается в конечном счете укусить Вас в торце.
C++ разработан для h файлов, имеющих объявления и cpp файлы, имеющие реализации. Компиляторы создаются вокруг этого дизайна.
Да , люди дебатируют, является ли это хорошей архитектурой, но это - дизайн. Лучше провести Ваше время на Вашей проблеме, чем перестраивание новых способов разработать архитектуру файла C++.
Одна вещь Вы бросаете это, мне было бы нелегко жить без, анонимные пространства имен.
я нахожу, что они невероятно ценны для определения определенных для класса служебных функций, которые должны невидимый вне файла реализации класса. Они являются также великими для содержания любых глобальных данных, которые должны быть невидимы для остальной части системы, как одноэлементный экземпляр.
Вы могли бы хотеть проверить Ленивый C++ . Это позволяет Вам помещать все в единственный файл, и затем это работает до компиляции и разделяет код на.h и .cpp файлы. Это могло бы предложить Вам лучший из обоих миров.
Медленное время компиляции обычно происходит из-за чрезмерной связи в системе, записанной в C++. Возможно, необходимо разделить код на подсистемы с внешними интерфейсами. Эти модули могли быть скомпилированы в отдельных проектах. Таким образом, можно минимизировать зависимость между различными модулями системы.
Очевидная оборотная сторона мне - то, что всегда необходимо создавать весь код сразу. С .cpp
файлы, у Вас может быть раздельная компиляция, таким образом, Вы только восстанавливаете биты, которые действительно изменились.
Вы неправильно понимаете, как язык был предназначен, чтобы использоваться. файлы .cpp действительно (или должен быть за исключением встроенного кода и шаблона кода), единственные модули исполняемого кода, который Вы имеете в своей системе. файлы .cpp компилируются в объектные файлы, которые тогда соединены. файлы.h существуют только для предописания кода, реализованного в .cpp файлах.
Это заканчивается в более быстрое время компиляции и меньший исполняемый файл. Это также значительно выглядит более чистым, потому что можно получить быстрый обзор класса путем рассмотрения его.h объявления.
Что касается встроенного кода и шаблона кода - потому что оба из них используются для генерации кода компилятором а не компоновщиком - они должны всегда быть доступны компилятору на .cpp файл. Поэтому единственное решение состоит в том, чтобы включать его в Ваш.h файл.
Однако я разработал решение, где у меня есть свое объявление класса в.h файле, всем шаблоне кода и встроенном коде в .inl файле и всей реализации не шаблон/встроенный код в моем .cpp файле. .inl файл является #included у основания моего.h файла. Это содержит вещи в чистоте и последовательный.
Вы правы сказать, что Ваше решение работает. Это не может даже иметь никаких недостатков для Вашего текущего проекта и среды разработки.
, Но...
, Поскольку другие заявили, помещение всего Вашего кода в заголовочных файлах вызывает полную компиляцию каждый раз, когда Вы изменяете одну строку кода. Это еще не может быть проблемой, но Ваш проект может стать достаточно большим ко времени компиляции точки, будет проблема.
Другая проблема при совместном использовании кода. В то время как Вы еще не можете быть непосредственно заинтересованы, важно сохранить как можно больше кода скрытым от потенциального пользователя Вашего кода. Путем помещения кода в заголовочный файл любой программист, использующий код, должен посмотреть целый код, в то время как там просто интересуются тем, как использовать его. Помещение Вашего кода в cpp файле позволяет только поставлять двоичный компонент (статическая или динамическая библиотека) и ее интерфейс как заголовочные файлы, которые могут быть более простыми в некоторой среде.
Это - проблема, если Вы хотите быть в состоянии превратить свой текущий код в динамическую библиотеку. Поскольку Вам не разъединили надлежащее объявление интерфейса от фактического кода, Вы не будете в состоянии поставить скомпилированную динамическую библиотеку и ее интерфейс использования как читаемые заголовочные файлы.
у Вас еще не может быть этих проблем, вот почему я говорил, что Ваше решение может быть в порядке в Вашей текущей среде. Но всегда лучше быть подготовленным к любому изменению, и некоторые из этих проблем должны быть решены.
пз: О C# или Java, необходимо иметь в виду, что эти языки не делают то, что Вы говорите. Они на самом деле компилируют файлы независимо (как cpp файлы), и хранит интерфейс глобально для каждого файла. Эти интерфейсы (и любые другие связанные интерфейсы) тогда используются для соединения целого проекта, вот почему они в состоянии обработать циклические ссылки. Поскольку C++ делает только одну передачу компиляции на файл, это не в состоянии глобально сохранить интерфейсы. Вот почему Вы обязаны писать им explicitely в заголовочных файлах.
Я не соглашаюсь с точкой 1.
Да, существует только один .cpp, и созданное время с нуля быстрее. Но, Вы редко создаете с нуля. Вы вносите небольшие изменения, и это должно было бы перекомпилировать целый проект каждый раз.
я предпочитаю делать его наоборот:
Так, некоторые мои .cpp файлы начинают быть похожими на Java или код C#;)
, Но, 'материал хранения в.h' подход хорош при разработке системы из-за точки 2. Вы сделали. Я обычно делаю это, в то время как я создаю иерархию классов и позже когда архитектура кода становится стабильной, я перемещаю код в .cpp файлы.
Один недостаток Вашего подхода - Вы, не может сделать параллельной компиляции. Вы могли бы думать, что получаете более быструю компиляцию теперь, но если у Вас есть несколько .cpp файлов, можно создать их параллельно, или на нескольких удаляют сердцевину на собственной машине или, или на использовании распределенной системы сборки как distcc или Incredibuild.