Ошибки компоновки и ODR
Заголовок triple.h под защитой #pragma once содержит не объявление, а определение функции: int triple(int x) { return x * 3; }. Его подключают два файла — report.cpp и вот этот main.cpp:
#include <iostream>
#include "triple.h" // ✗
int main() {
std::cout << triple(14) << '\n';
}Каждый файл компилируется без ошибок, но сборка clang++ main.cpp report.cpp останавливается на компоновке:
multiple definition of `triple(int)'Правило одного определения
Компоновщик (linker) собирает в программу объектные файлы всех единиц трансляции. Каждая из двух единиц получила из заголовка своё определение triple, и в программе их стало два. Правило одного определения (one definition rule, ODR) это запрещает: в программе не больше одного определения функции, а у вызванной — ровно одно. #pragma once здесь не поможет: копии лежат в разных единицах трансляции.
Нарушить правило можно и с другой стороны. Если triple.h содержит только объявление, а triple.cpp не указали в команде сборки, все файлы скомпилируются: объявление есть в каждом. Тогда компоновщик сообщит undefined reference, а в задачах курса — undefined symbol. Он называет функцию, а не файл: найдите .cpp-файл с её определением и проверьте, что он есть в команде.
inline в заголовке
Если определение должно остаться в заголовке, функцию объявляют inline:
#pragma once
inline int triple(int x) {
return x * 3;
}Слово inline разрешает определению функции повторяться в разных единицах трансляции, если все копии одинаковы. Копии из одного заголовка одинаковы всегда, и компоновщик оставляет одну из них. Функции constexpr тоже считаются inline, поэтому их определяют прямо в заголовках. Остальные функции обычно делят: объявление — в заголовке, определение — в .cpp-файле.
Частая ошибка
Вместо заголовка подключили .cpp-файл:
#include <iostream>
#include "triple.cpp" // ✗
int main() {
std::cout << triple(14) << '\n';
}Сам main.cpp компилируется, но сборка clang++ main.cpp triple.cpp снова заканчивается ошибкой multiple definition. Определение triple попало в две единицы трансляции: в triple.cpp и в main.cpp, куда его вставил #include. Подключайте заголовок triple.h, а .cpp-файлы передавайте только команде сборки. В задачах курса тесты тоже подключают .cpp-файл с решением, но там вся сборка — одна единица трансляции.