Не включая соответствующий файл заголовка (.h) в файл реализации (.cpp) все еще компилируется? - PullRequest
1 голос
/ 22 апреля 2019

Сегодня я написал короткий пример, чтобы посмотреть, скомпилируется ли он, и я был очень удивлен, когда обнаружил, что это сработало!

Вот пример:

hello.h

#ifndef HELLO_H
#define HELLO_H

// Function prototype
void say_hello();

#endif

hello.cpp

ПРИМЕЧАНИЕ: Это НЕ включает "hello.h", как это было бы в каждом примере C ++, который я когда-либо видел в истории навсегда!

// #include "hello.h" <-- Commented out. The corresponding header is NOT included.

#include <iostream>

void say_hello() {
    std::cout << "Hello!" << std::endl;
}

main.cpp

#include "hello.h"

int main() {
    say_hello();
}

Затем я скомпилировал "hello.cpp" в статическую библиотеку следующим образом:

g++ -c hello.cpp
ar -rvs libhello.a hello.o

Затем я скомпилировал «основное» приложение и связал его с библиотекой

g++ -o main main.cpp -L. -lhello

И запустил его, и он прекрасно выполнил!

./main

Здравствуйте!


Хотя я был удивлен ... Я понимаю, почему это работает. Это потому, что функция в "hello.cpp" не объявлена ​​как статическая, поэтому имеет внешнюю связь и может быть видна снаружи. Установка статического состояния приведет к сбою ссылки из-за неопределенной ссылки.

Так вот в чем вопрос ... Если это работает, то почему все ВСЕГДА включают заголовочный файл ".h" с объявлениями функций в файле реализации ".cpp". Ясно, что если это просто определение свободных функций, это не обязательно, и все будет работать нормально, если заголовочный файл не включен.

Так почему мы всегда включаем это? - Это просто общее непонимание того, как работает компоновщик? Или есть что-то еще?

Ответы [ 2 ]

2 голосов
/ 23 апреля 2019

Позвольте нам изменить ваш hello.cpp:

// #include "hello.h" <-- Commented out. The corresponding header is NOT included.

#include <iostream>

int say_hello() {
    std::cout << "Hello!" << std::endl;
    return 0;
}

Это скомпилируется так же, как и предыдущая версия.Вероятно, это тоже будет ссылка - но это не правильно.Тип возврата неверный.

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

Если, с другой стороны, вы написали:

#include "hello.h"

#include <iostream>

int say_hello() {
    std::cout << "Hello!" << std::endl;
    return 0;
}

Тогда объявление в заголовочном файле не соответствовало бы определению в файле CPP - и вы получили бы хорошее, простое для понимания сообщение об ошибке компилятора.

InНа самом деле, это такая хорошая идея, что GCC будет жаловаться, если у вас нет объявления внешней функции.(И если в командной строке есть -wall -werror, сборка будет остановлена.)

0 голосов
/ 22 апреля 2019

Если у вас есть класс, вы захотите включить его, чтобы получить объявление класса и его членов для определения их соответствия. В противном случае вы не сможете разделить определение и объявление.

/// C.h
class C
{
public:
    C();
private:
    int _i;
};
/// C.cpp
// #include "C.h"

C::C() : _i(42) {} // error: 'C' does not name a type

См., Что это терпит неудачу на Coliru .

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

...