Должен ли я помещать много функций в один файл? Или, более или менее, одна функция для каждого файла?

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

Причины:

  • Когда я прочитаю код, я всегда буду знать, в каком файле я должен найти определенной функции или класса.

  • Если это один класс или один нечлен функции в файле заголовка, тогда я не буду включать целый беспорядок, когда я include заголовочный файл.

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

Однако, разбивая все на многие заголовки, и многие файлы реализации могут значительно замедлить компиляцию. В моем проекте большинство функций обращаются к определенному числу шаблонных других функций библиотеки. Так что код будет компилироваться снова и снова, один раз для каждого файла реализации. Компиляция всего моего проекта в настоящее время занимает 45 минут или около того на одном компьютере. Существует около 50 объектных файлов, и каждый из них использует те же самые дорогие компиляции заголовков.

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

// foo.h
void foo(int n);

// bar.h
void bar(double d);

// foobar.cpp
#include <vector>
void foo(int n) { std::vector<int> v; ... }
void bar(double d) { std::vector<int> w; ... }

Опять же, преимущество состоит в том, что я могу включить только функцию foo или только функцию бара, а компиляция всего проекта будет быстрее, потому что foobar.cpp - это один файл, поэтому std::vector<int> (который является просто пример здесь для какой-либо другой дорогостоящей компиляции шаблонов) должен быть скомпилирован только один раз, а не в два раза, если я скомпилировал foo.cpp и bar.cpp отдельно. Конечно, моя причина (3) выше не подходит для этого сценария: После простого изменения foo() {...} мне нужно перекомпилировать весь потенциально большой файл foobar.cpp.

Мне любопытно, каковы ваши мнения!

Ответ 1

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

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

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

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

Ответ 2

На мой взгляд, одна функция на файл может быть беспорядочной. Представьте, что заголовки POSIX и ANSI C были сделаны одинаково.

#include <strlen.h>
#include <strcpy.h>
#include <strncpy.h>
#include <strchr.h>
#include <strstr.h>
#include <malloc.h>
#include <calloc.h>
#include <free.h>
#include <printf.h>
#include <fprintf.h>
#include <vpritnf.h>
#include <snprintf.h>

Один класс для файла - хорошая идея.

Ответ 3

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

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

// getShapeColor.h
Color getShapeColor(Shape);

// getTextColor.h
Color getTextColor(Text);

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

При этом даже в случае стандартной библиотеки есть некоторые потенциальные преимущества в разделении отдельных функций. Во-первых, компиляторы могут генерировать полезное предупреждение при использовании небезопасных версий функций, например, strcpy и strncpy, аналогично тому, как g++ предупреждал о включении & lt; iostream.h> против & lt; iostream>.

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

Ответ 4

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

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

Ответ 5

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

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

Ответ 6

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

Если бы я все еще программировал на С++, я бы группировал функции с использованием нескольких функций для каждого файла. Поэтому у меня может быть файл под названием "Service.cpp" с несколькими функциями, которые определяют этот "сервис". Наличие одной функции в файле, в свою очередь, вызывает сожаление, чтобы как-то вернуться к вашему проекту.

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

Никогда не помешает иметь несколько исходных файлов, которые определяют единый объект. Т.е.: "ServiceConnection.cpp" "ServiceSettings.cpp" и т.д. И т.д.

Иногда, если я создаю отдельный объект и владею другими объектами, я объединю несколько классов в один файл. Например, кнопочный элемент управления, содержащий объекты ButtonLink, я мог бы объединить это в класс Button. Иногда я этого не делаю, но это решение "предпочтения момента".

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

Ответ 7

Для части заголовка вы должны объединить элементы в логические группы и создать файлы заголовков на основе этого. Это кажется и очень логичным ИМХО.

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

Оформить заказ glibc или Visual C++ ЭЛТ-исходное дерево...

Ответ 8

Я вижу некоторые преимущества вашего подхода, но есть несколько недостатков:

  1. В том числе пакет это кошмар. Вы можете получить 10-20 включений, чтобы получить нужные вам функции. Например, представьте, был ли реализован STDIO или StdLib таким образом.

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

  3. Сделать обслуживание файлов - это боль.

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

Ответ 9

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

Статические библиотеки связаны с детализацией объектного файла, поэтому, если у вас есть статическая библиотека libfoobar.a, состоящая из *:

 foo.o
     foo1
     foo2
 bar.o
     bar

то если вы связываете lib для функции bar, член архива bar.o будет связан, но не член foo.o. Если вы ссылаетесь на foo1, член foo.o будет связан, что приведет к ненужной функции foo2.

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


  • Я просто игнорирую название С++ для упрощения.