Добавление пространств имен в реализации C ++, имеющие заголовок C - PullRequest
4 голосов
/ 14 ноября 2011

У нас есть большой проект с кодом C и C ++.

Для каждой реализации C ++, кроме заголовка C ++, мы обычно предоставляем C-заголовок, чтобы обеспечить доступность функциональности для файлов .c,также.

Итак, большинство наших файлов выглядят так:

foo.hpp:

class C { 
    int foo();
};

foo.h:

#ifdef __cplusplus
extern "C" {
    typedef struct C C;  // forward declarations
#else
    class C;
#endif

    int foo( C* );               // simply exposes a member function
    C*  utility_function( C* );  // some functionality *not* in foo.hpp  

#ifdef __cplusplus
}
#endif

foo.cpp:

int C::foo()  { /* implementation here...*/ }

extern "C"
int foo( C *p ) { return p->foo(); }

extern "C"
C*  utility_function ( C* ) { /* implementation here...*/ }

ВОПРОС:

Предположим, яхотел добавить пространство имен к классу, например, так:

foo.hpp:

namespace NS {
    class C { 
        int foo();
    };
}

Какова лучшая схема для использования в C-заголовках?

Я рассмотрел несколько вариантов, но ищу наиболее элегантный, безопасный и удобный для чтения.Есть ли стандартный способ, которым вы пользуетесь?


Вот варианты, которые я рассмотрел: (Я упустил конструкции extern "C" для простоты)

  • Вариант 1: обманывает компилятор, добавляя код в каждый заголовок:

foo.h

#ifdef __cplusplus
    namespace NS { class C; }  // forward declaration for C++ 
    typedef NS::C NS_C;
#else
    struct NS_C;  // forward declaration for C
#endif

int foo( NS_C* );
NS_C*  utility_function( NS_C* );

добавляет сложностизаголовок, но сохраняет реализации без изменений.


  • Опция 2: Обертка пространства имен с C-struct:

    Сохраняетзаголовок простой, но делает реализацию более сложной:

foo.h

struct NS_C;  // forward declaration of wrapper (both for C++ and C)

int foo( NS_C* );
NS_C*  utility_function( NS_C* );

foo.cpp

namespace NS {
    int C::foo() { /* same code here */ }
}

struct NS_C {     /* the wrapper */
    NS::C *ptr;
};

extern "C" 
int foo( NS_C *p ) { return p->ptr->foo(); }

extern "C"
NS_C *utility_function( NS_C *src ) 
{
    NS_C *out = malloc( sizeof( NS_C ) );  // one extra malloc for the wrapper here...
    out->ptr = new NS::C( src->ptr );
    ...
}

это единственные схемы?Есть ли скрытые недостатки в любом из них?

Ответы [ 3 ]

2 голосов
/ 14 ноября 2011

Я считаю, что проще кодировать код таким образом, чтобы 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 , поэтомуэто на самом деле не так уж плохо, и если вы заботитесь о переносимости, я бы сказал, пойти на это.

1 голос
/ 14 ноября 2011

Я не совсем понимаю, что вы пытаетесь сделать, но это может помочь:

Если вы хотите, чтобы он все еще был доступен для C, сделайте следующее:

void foo();

namespace ns {
  using ::foo;
}

Илииспользуйте макрос:

#ifdef __cplusplus
#define NS_START(n) namespace n {
#define NS_END }
#else
#define NS_START(n)
#define NS_END
#endif

NS_START(ns)
void foo();
NS_END
0 голосов
/ 14 ноября 2011

Ваш заголовок испорчен.

Вы, вероятно, хотите что-то похожее на:

struct C;

#ifdef __cplusplus
extern "C" {
#else
typedef struct C C;
#endif

/* ... */

#ifdef __cplusplus
}
#endif
...