Для начала, правильный путь — это конечно вынести прототипы отдельно, а имплементацию отдельно. Таким путём вы получаете преимущества раздельной компиляции: ваши функции компилируются отдельно, лишь один раз.
В случае, когда функции находятся в header'е, они будут компилироваться каждый раз при включении этого самого header'а. Кроме того, вам придётся каждую функцию обозначить как inline
, чтобы компоновщик не ругался на множественные определения.
(Немного офтопика: inline
в C++ не означает «встрой эту функцию в место вызова вместо реального вызова». Такими оптимизациями заведует компилятор и только он. Смысл inline
именно такой: сообщить компоновщику, что функции с одним именем, которые он видит в разных модулях — это одна и та же функция, и не нужно выдавать ошибку.)
Ещё одна проблема, которую позволяют изящно решать header
'ы — зависимости. Например, рассмотрим взаимно-рекурсивные функции. Когда вы пользуетесь функцией f
, вы можете вызвать в ней другую функцию g
. Допустим, вы пишете ваши функции в header'е. Если функция g
определена ниже функции f
, вам придётся ещё и добавлять декларацию функции g
. В случае, если имплементация находится вне header'а, проблема не возникает (но зато, конечно, вам приходится делать декларации всех функций).
(Особенно остро проблема зависимостей стоит в случае классов, содержащих ссылки друг на друга, т. к. в header всё равно обычно выносят определение данных класса, включая приватные поля!)
Существуют, однако, случаи, когда код приходится держать в header'е. Например, шаблонные функции (да и классы) должны быть в нём, иначе у вас могут быть проблемы с инстанциацией.