在C中包装C ++:派生到基本转换

时间:2012-02-06 22:50:24

标签: c++ c oop standard-layout

我正在将一个简单的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()进行虚拟调用。

这些类不是standard-layout,但不使用多重或虚拟继承。这保证有效吗?

请注意,如果重要的话,我可以控制所有代码(C ++和C包装器)。

4 个答案:

答案 0 :(得分:4)

你当然可以为某些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函数传播!

答案 1 :(得分:3)

// 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*>
  8. 对于包装FirstDerived方法的调用,您将获得额外的强制转换:

    1. FirstDerived*(通过dynamic_cast<FirstDerived*>

答案 2 :(得分:1)

这是我过去使用的方法(也许是Aaron评论所暗示的)。请注意,相同的类型名称在C和C ++中都使用。转换都是用C ++完成的;无论合法性问题如何,这自然代表了良好的封装。 [显然你也需要delete个方法。 请注意,要使用someOperation()来呼叫Derived*,需要明确向Base*转播。 如果 Derived没有提供任何新方法,例如someOtherOperation,那么您不需要向客户端公开Derived*,并避免客户端强制转换。< / p>

标题文件:“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();
}

答案 3 :(得分:0)

我认为这两行是问题的核心:

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代码。但我会担心编译器会允许各种疯狂的强制转换,例如{{1}或者甚至强制转换为不同类层次结构中的类型。我的目标是在C代码中强制执行适当的面向对象的 is-a 关系。)

然后,C句柄类可能类似于

(Derived1*) derived2_ptr

这样可以很容易地以简洁安全的方式使用类似强制转换的内容:请注意我们如何在此代码中传入struct base_t_ptr { void * this_; // holds the Base pointer }; typedef struct { struct base_t_ptr get_base; } derived_t_ptr;

object.get_base

其中base_some_operation的声明是

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

这将是非常类型安全的,因为如果不通过extern "C" base_some_operation(struct base_t_ptr); 数据成员,您将无法将derived1_t_ptr传递给此函数。它还可以帮助您的C代码了解类型和转换的有效性 - 您不希望意外地将Derived1转换为Derived2。

然后,当实现仅在派生类中定义的非虚拟方法时,您需要以下内容:

.get_base