Порядок включения заголовков C , странное поведение

#c #include #conditional-compilation

Вопрос:

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

Скажем, у меня есть класс SomeClass с методом int foo(std::string) . Иногда очень полезно также иметь аналогичные методы, которые используют классы другой библиотеки , на которой построен проект, например, sf::String или wxString , соответственно, для SFML или wxWidgets.

В этом случае включение SFML/System.hpp или даже хуже, wx/app.hpp или подобное абсолютно НЕ вариант, потому что я хочу иметь только методы для библиотек, которые уже включены. Итак, мой первый пример должен (как я полагаю) работать нормально, но это не так:

main.cpp:

 #include <SFML/System.hpp>       // FIRST, I include SFML base lib in the very first line.
#include <SFML/System/String.hpp>// to be 100% sure, I include SFML string class,
#include "a.h"                   // and ONLY AFTER that I include my own lib
// so inside the "a.h" file, the sf::String class *must* be already declared
main()
{   SomeClass x;
    x.foo("ABC");// error here: "undefined reference to `SomeClass::foo(sf::String)"
}
 

a.h:

 #ifndef A_H_INCLUDED
#define A_H_INCLUDED
class SomeClass
{   public:
    #ifdef SFML_STRING_HPP
    int foo(sf::String str);// this method is declared, as expected
    #endif
};
#endif
 

a.cpp:

 #include "a.h"
#ifdef SFML_STRING_HPP
int SomeClass::foo(sf::String str)
{   return 1;
}
#endif
 

Первый вопрос: ПОЧЕМУ? a.cpp включает a.h в себя в самом начале, а внутри a.h sf::String объявлено, так почему же внутри a.cpp после #include "a.h" того, как оно не объявлено на самом деле?

Я попытался добавить #error OK #endif директиву прямо перед вводом в a.cpp файл, и эта ошибка не сработала.

Я что-то пропустил о #include и .cpp / .h файлах?..

Второй вопрос: как это исправить или обойти?

(И да, я каждый раз делаю чистую перестройку, чтобы избежать возможных ошибок компилятора в отношении частично измененных источников, g это нравится).

P. S: Такого же рода объявления «зависимых» методов отлично работают с некоторыми классами шаблонов — я полагаю, это потому, что реализация находится в .h файле, где все в порядке с условной компиляцией.

Комментарии:

1. Небольшой совет: вы можете сделать #include операторы также необязательными, поместив их в #ifdef области препроцессора. Таким образом, вы можете включить заголовок строки в то же условие, которое вы объявляете, и определить функцию, которая его использует.

2. Определения классов, которые меняются в зависимости от контекста, рано или поздно вас укусят. Не делай этого.

3. Пит, почему он будет кусаться? Я объявляю и определяю разные методы, поэтому в проекте на основе SFML я буду использовать fooSM(sf::String) , в проекте на основе wxWidgets я буду использовать fooWX(wxString) метод. Это позволяет мне сохранять основной код библиотеки одинаковым для многих различных проектов, а также избегать избыточного преобразования типов (используйте собственные типы в зависимости от базовых библиотек проекта).

Ответ №1:

a.c включает в себя a.h, который не включает <SFML/System/String.hpp>, поэтому SFML_STRING_HPP не определен. Обычно то, что нужно включить, задается с помощью параметров компилятора-D. Например-DUSE_SFML_STRING

main.cpp

 #include <SFML/System.hpp>       // FIRST, I include SFML base lib in the very first line.
#include "a.h"                   // and ONLY AFTER that I include my own lib
// so inside the "a.h" file, the sf::String class *must* be already declared
main()
{   SomeClass x;
    x.foo("ABC");// error here: "undefined reference to `SomeClass::foo(sf::String)"
}
 

a.h

 #ifndef A_H_INCLUDED
#define A_H_INCLUDED

#ifdef USE_SFML_STRING
#include <SFML/System/String.hpp>
#endif

class SomeClass
{   public:
    #ifdef SFML_STRING_HPP
    int foo(sf::String str);// this method is declared, as expected
    #endif
};
#endif
 

Комментарии:

1. Сначала я думал, что все скомпилировано, начиная с main.cpp и в том числе то, что необходимо, но это не так. Добавление определения для всего проекта полностью решает мою проблему. Спасибо!