5

I am wrapping a simple C++ inheritance hierarchy into "object-oriented" C. I'm trying to figure out if there any gotchas in treating the pointers to C++ objects as pointers to opaque C structs. In particular, under what circumstances would the derived-to-base conversion cause problems?

The classes themselves are relatively complex, but the hierarchy is shallow and uses single-inheritance only:

// 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..

I am planning on exposing this to C clients via the following, fairly standard object-oriented 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;
}

The C clients only pass around pointers to the underlying C++ objects and can only manipulate them via function calls. So the client can finally do something like:

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

and have the virtual call to FirstDerived::someOperation() succeed as expected.

These classes are not standard-layout but do not use multiple or virtual inheritance. Is this guaranteed to work?

Note that I have control over all the code (C++ and the C wrapper), if that matters.

4

4 に答える 4

4
// 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 ++スタイルのvia reinterpret_cast<base_t*>
  6. void*(暗黙的にstatic_cast<void*>
  7. Base*(経由reinterpret_cast<Base*>

メソッドをラップする呼び出しのFirstDerived場合、追加のキャストを取得します。

  1. FirstDerived*(経由dynamic_cast<FirstDerived*>
于 2012-02-07T00:52:49.897 に答える
4

あなたは確かにいくつかのC++コードへのCインターフェースを作ることができます。必要なのは、です。不透明(OPAQUE)型としてextern "C"aをお勧めします。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関数を介して例外が伝播することを決して許可しないでください

于 2012-02-06T23:08:17.353 に答える
1

これは私が過去に使用したアプローチです(おそらくアーロンのコメントによって暗示されています)。CとC++の両方で同じ型名が使用されていることに注意してください。キャストはすべてC++で行われます。これは、合法性の問題に関係なく、当然、適切なカプセル化を表します。[もちろん、メソッドも必要ですdelete。]を使用して呼び出すsomeOperation()にはDerived*、への明示的なアップキャストBase*が必要であることに注意してください。がなどの新しいメソッドを提供しない場合 は、クライアントに公開する必要はなく、クライアント側のキャストを回避します。DerivedsomeOtherOperationDerived*

ヘッダーファイル: "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();
}
于 2012-02-07T00:35:25.307 に答える
0

私はこれらの2行が質問の要点だと思います:

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

Cコードでこれを許可する本当に安全な方法はありません。Cでは、このようなキャストによってポインターの生の整数値が変更されることはありませんが、C ++キャストによって変更される場合があるため、Cコード内にキャストがないデザインが必要になります。

これが1つの(過度に複雑な?)解決策です。まず、Cコードが常に効果的に値を厳密に処理するというポリシーを決定しますBase*。これは、一貫性を確保するためのやや恣意的なポリシーです。これは、C ++コードがdynamic_castを実行する必要がある場合があることを意味します。これについては、後で説明します。

(他の人が言及しているように、キャストを使用するだけでCコードでデザインを正しく機能させることができます。しかし、コンパイラーがあらゆる種類のクレイジーなキャストを許可するのではないかと心配しています(Derived1*) derived2_ptr。異なるクラス階層。ここでの私の目標は、適切なオブジェクト指向のis- 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データメンバーを経由せずにderived1_t_ptrをこの関数に渡すことはできないため、これは非常にタイプセーフです。また、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();
}
于 2012-02-07T00:32:50.827 に答える