Обтекание C ++ в C: Производные к базовым преобразованиям - PullRequest
5 голосов
/ 07 февраля 2012

Я оборачиваю простую иерархию наследования C ++ в «объектно-ориентированную» C. Я пытаюсь выяснить, есть ли какие-то хитрости в обработке указателей на объекты C ++ как указателей на непрозрачные структуры C.В частности, при каких обстоятельствах преобразование из производной в базу может вызвать проблемы?

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

// A base class with lots of important shared functionality
class Base {
    public:
    virtual void someOperation();
    // More operations...

    private:
    // Data...
};

// One of several derived classes
class FirstDerived: public Base {
    public:
    virtual void someOperation();
    // More operations...

    private:
    // More data...
};

// More derived classes of Base..

Я планирую раскрыть это клиентам C через следующую, довольно стандартную объектно-ориентированную C:

// An opaque pointers to the types
typedef struct base_t base_t;
typedef struct first_derived_t first_derived_t;

void base_some_operation(base_t* object) {
     Base* base = (Base*) object;
     base->someOperation();
}

first_derived_t* first_derived_create() {
     return (first_derived_t*) new FirstDerived();
}

void first_derived_destroy(first_derived_t* object) {
     FirstDerived* firstDerived = (FirstDerived*) object;
     delete firstDerived;
}

Клиенты C только передают указатели на базовые объекты C ++ и могут манипулировать ими только через вызовы функций,Таким образом, клиент может, наконец, сделать что-то вроде:

first_derived_t* object = first_derived_create();
base_some_operation((base_t*) object); // Note the derived-to-base cast here
...

и сделать виртуальный вызов FirstDerived :: someOperation () успешным, как и ожидалось.

Эти классы не являются стандартным макетом но не используйте множественное или виртуальное наследование.Это гарантированно сработает?

Обратите внимание, что я могу контролировать весь код (C ++ и оболочку C), если это имеет значение.

Ответы [ 4 ]

4 голосов
/ 07 февраля 2012

Вы, конечно, можете создать интерфейс C для некоторого кода C ++. Все, что вам нужно, это extern "C", и я рекомендую void * в качестве вашего непрозрачного типа данных:

// library.h, for C clients

typedef void * Handle;

extern "C" Handle create_foo();
extern "C" void destroy_foo(Handle);

extern "C" int magic_foo(Handle, char const *);

Затем реализуйте это в C ++:

#include "library.h"
#include "foo.hpp"

Handle create_foo()
{
    Foo * p = nullptr;

    try { p = new Foo; }
    catch (...) { p = nullptr; }

    return p
}

void destroy_foo(Handle p)
{
    delete static_cast<Foo*>(p);
}

int magic_foo(Handle p, char const * s)
{
    Foo * const f = static_cast<Foo*>(p);

    try
    {
        f->prepare();
        return f->count_utf8_chars(s);
    }
    catch (...)
    {
        return -1;
        errno = E_FOO_BAR;
    }
}

Не забудьте никогда не разрешать любым исключениям распространяться через вызывающую функцию C!

3 голосов
/ 07 февраля 2012
// An opaque pointers to the types
typedef struct base_t base_t;
typedef struct first_derived_t first_derived_t;

// **********************//
// inside C++ stub only. //
// **********************//

// Ensures you always cast to Base* first, then to void*,
// then to stub type pointer.  This enforces that you'll
// get consistent a address in presence of inheritance.
template<typename T>
T * get_stub_pointer ( Base * object )
{
     return reinterpret_cast<T*>(static_cast<void*>(object));
}

// Recover (intermediate) Base* pointer from stub type.
Base * get_base_pointer ( void * object )
{
     return reinterpret_cast<Base*>(object);
}

// Get derived type pointer validating that it's actually
// the right type.  Returs null pointer if the type is
// invalid.  This ensures you can detect invalid use of
// the stub functions.
template<typename T>
T * get_derived_pointer ( void * object )
{
    return dynamic_cast<T*>(get_base_pointer(object));
}

// ***********************************//
// public C exports (stub interface). //
// ***********************************//

void base_some_operation(base_t* object)
{
     Base* base = get_base_pointer(object);
     base->someOperation();
}

first_derived_t* first_derived_create()
{
     return get_stub_pointer<first_derived_t>(new FirstDerived());
}

void first_derived_destroy(first_derived_t* object)
{
     FirstDerived * derived = get_derived_pointer<FirstDerived>(object);
     assert(derived != 0);

     delete firstDerived;
}

Это означает, что вы всегда можете выполнить приведение, например, следующее.

first_derived_t* object = first_derived_create();
base_some_operation((base_t*) object);

Это безопасно, потому что указатель base_t* будет приведен к void*, затем к Base*. Это на один шаг меньше, чем было раньше. Обратите внимание на заказ:

  1. FirstDerived*
  2. Base* (через неявное static_cast<Base*>)
  3. void* (через static_cast<void*>)
  4. first_derived_t* (через reinterpret_cast<first_derived_t*>)
  5. base_t* (через (base_t*), стиль C ++ reinterpret_cast<base_t*>)
  6. void* (через неявное static_cast<void*>)
  7. Base* (через reinterpret_cast<Base*>)

За вызовы, заключающие метод FirstDerived, вы получаете дополнительное приведение:

  1. FirstDerived* (через dynamic_cast<FirstDerived*>)
1 голос
/ 07 февраля 2012

Это подход, который я использовал в прошлом (возможно, как подразумевается в комментарии Аарона). Обратите внимание, что одинаковые имена типов используются как в C, так и в C ++. Все приведения сделаны в C ++; это естественно представляет собой хорошую инкапсуляцию независимо от вопросов законности. [Очевидно, вам также нужны delete методы.] Обратите внимание, что для вызова someOperation() с Derived* требуется явное повышение до Base*. Если Derived не предоставляет никаких новых методов, таких как someOtherOperation, то вам не нужно выставлять Derived* клиентам и избегать приведения на стороне клиента.

Заголовочный файл: "BaseDerived.H"

#ifdef __cplusplus
extern "C"
{
#endif
    typedef struct Base Base;
    typedef struct Derived Derived;

    Derived* createDerived();
    Base* createBase();
    Base* upcastToBase(Derived* derived);
    Derived* tryDownCasttoDerived(Base* base);
    void someOperation(Base* base);
void someOtherOperation(Derived* derived);
#ifdef __cplusplus
}
#endif

Реализация: "BaseDerived.CPP"

#include "BaseDerived.H"
struct Base 
{
    virtual void someOperation()
    {
        std::cout << "Base" << std::endl;
    }
};
struct Derived : public Base
{
public:
    virtual void someOperation()
    {
        std::cout << "Derived" << std::endl;
    }
private:
};

Derived* createDerived()
{
    return new Derived;
}

Base* createBase()
{
    return new Base;
}

Base* upcastToBase(Derived* derived)
{
    return derived;
}

Derived* tryDownCasttoDerived(Base* base)
{
    return dynamic_cast<Derived*>(base);
}

void someOperation(Base* base)
{
    base->someOperation();
}

void someOperation(Derived* derived)
{
    derived->someOperation();
}
0 голосов
/ 07 февраля 2012

Я думаю, что эти две строки являются суть вопроса:

first_derived_t* object = first_derived_create();
base_some_operation((base_t*) object); // Note the derived-to-base cast here
...

Нет действительно безопасного способа разрешить это в коде C.В C такое приведение никогда не изменяет необработанное целочисленное значение указателя, но иногда приведение C ++ будет делать это, и поэтому вам нужен дизайн, который никогда не будет иметь приведений в коде C.

Вот один из них (чрезмерносложное?) решение.Во-первых, определитесь с политикой, что код C всегда будет строго иметь дело со значением, которое фактически является Base* - это несколько произвольная политика для обеспечения согласованности.Это означает, что код C ++ иногда должен быть в dynamic_cast, мы вернемся к этому позже.

(Вы можете заставить проект работать правильно с кодом C, просто используя приведение, как уже упоминалось другимиНо я был бы обеспокоен тем, что компилятор разрешит все виды сумасшедших приведений, таких как (Derived1*) derived2_ptr или даже приведение типов к другой иерархии классов. Моя цель здесь состоит в том, чтобы обеспечить правильную объектно-ориентированную -a отношения в коде C.)

Тогда классы дескрипторов C могут быть примерно такими:

struct base_t_ptr {
    void * this_; // holds the Base pointer
};
typedef struct {
    struct base_t_ptr get_base;
} derived_t_ptr;

Это должно упростить использование чего-то вроде приведений в сжатом видеи безопасный способ: обратите внимание, как мы передаем object.get_base в этом коде:

first_derived_t_ptr object = first_derived_create();
base_some_operation(object.get_base);

, где объявление base_some_operation

extern "C" base_some_operation(struct base_t_ptr);

Это будет довольно безопасно для типов, так как вы выигралине сможет передать производный_т_птр в эту функцию без использования элемента данных .get_base.Это также поможет вашему коду на C немного узнать о типах и о том, какие преобразования допустимы - вы не хотите случайно конвертировать Derived1 в Derived2.

Затем при реализации не виртуальных методов, определенных только в производном классе, вам понадобится что-то вроде:

extern "C" void derived1_nonvirtual_operation(struct derived1_t_ptr); // The C-style interface. Type safe.

void derived1_nonvirtual_operation(struct derived1_t_ptr d) {
    // we *know* this refers to a Derived1 type, so we can trust these casts:
    Base * bp = reinterpret_cast<Base*>(d.get_base.this_);
    Derived1 *this_ = dynamic_cast<Derived1*>;
    this_ -> some_operation();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...