Я считаю, что проще кодировать код таким образом, чтобы foo.h
содержал только минимальный набор спецификаций C ++, а foo.hpp
заботился о песчаных битах.
Файл foo.h содержит API C и не должен быть включен непосредственно из кода C ++:
#ifndef NS_FOO_H_
#define NS_FOO_H_
// an incomplete structure type substitutes for NS::C in C contexts
#ifndef __cplusplus
typedef struct NS_C NS_C;
#endif
NS_C *NS_C_new(void);
void NS_C_hello(NS_C *c);
#endif
Файл foo.hpp содержит актуальный API C ++ и заботится о включении foo.h в файлы C ++:
#ifndef NS_FOO_HPP_
#define NS_FOO_HPP_
namespace NS {
class C {
public:
C();
void hello();
};
}
// use the real declaration instead of the substitute
typedef NS::C NS_C;
extern "C" {
#include "foo.h"
}
#endif
Файл реализации foo.cpp написан на C ++ и, таким образом, включает foo.hpp , которыйтакже извлекает foo.h :
#include "foo.hpp"
#include <cstdio>
using namespace NS;
C::C() {}
void C::hello() {
std::puts("hello world");
}
C *NS_C_new() {
return new C();
}
void NS_C_hello(C *c) {
c->hello();
}
Если вы не хотите делать C API доступным для кода C ++, вы можете переместить соответствующие части из foo.hpp до foo.cpp .
В качестве примера использования базового файла C API main.c :
#include "foo.h"
int main(void)
{
NS_C *c = NS_C_new();
NS_C_hello(c);
return 0;
}
Этот пример был протестирован с выпуском MinGW gcc 4.6.1 с использованием следующих флагов компилятора:
g++ -std=c++98 -pedantic -Wall -Wextra -c foo.cpp
gcc -std=c99 -pedantic -Wall -Wextra -c main.c
g++ -o hello foo.o main.o
Код предполагает, что типы NS::C *
и struct NS_C *
имеют compПодходящие требования к представлению и выравниванию, которые должны иметь место практически везде, но, насколько я знаю, не гарантируется стандартом C ++ (не стесняйтесь исправлять меня, если я здесь не прав).
С точки зрения языка C код фактически вызывает неопределенное поведение, поскольку вы технически вызываете функцию через выражение несовместимого типа, но это цена взаимодействия без структур-оболочек и приведения указателей:
Поскольку C не знает, как обращаться с указателями классов C ++, переносимое решение будет использовать void *
, который вам, вероятно, следует заключить в структуру, чтобы получить некоторый уровень безопасности типов:
typedef struct { void *ref; } NS_C_Handle;
Это добавит ненужный шаблон на платформах с единообразным представлением указателя:
NS_C_Handle NS_C_new() {
NS_C_Handle handle = { new C() };
return handle;
}
void NS_C_hello(NS_C_Handle handle) {
C *c = static_cast<C *>(handle.ref);
c->hello();
}
С другой стороны, это избавит от #ifndef __cplusplus
в foo.h , поэтомуэто на самом деле не так уж плохо, и если вы заботитесь о переносимости, я бы сказал, пойти на это.