我可以逃脱这个C ++向下转型吗?

时间:2011-08-24 21:37:18

标签: c++

我有一个C库,其类型如下:

typedef struct {
  // ...
} mytype;

mytype *mytype_new() {
  mytype *t = malloc(sizeof(*t));
  // [Initialize t]
  return t;
}

void mytype_dosomething(mytype *t, int arg);

我想提供C ++包装器来提供更好的语法。但是,我想避免使用单独分配的包装器对象的复杂性。我有一个相对复杂的对象图,其内存管理已经比我想要的更复杂(对象以所有可到达对象保持活动的方式被重新计算)。此外,C库将使用指向此对象的指针回调到C ++,并且为每个C-> C ++回调构建一个新的包装器对象的成本(因为C不知道包装器)对我来说是不可接受的。

我的一般方案是:

class MyType : public mytype {
 public:
   static MyType* New() { return (MyType*)mytype_new(); }
   void DoSomething(int arg) { mytype_dosomething(this, arg); }
};

这将为C ++程序员提供更好的语法:

// C Usage:
mytype *t = mytype_new();
mytype_dosomething(t, arg);

// C++ Usage:
MyType *t = MyType::New();
t->DoSomething(arg);

我认为我正在向mytype*转发一个malloc()(用MyType*分配),这是一个谎言。但是如果MyType没有成员而没有虚函数,那么我似乎应该能够依赖sizeof(mytype) == sizeof(MyType),除了MyType之外没有编译器可以生成任何类型的实际数据参考。

所以尽管这可能违反了C ++标准,但我很想我可以通过各种编译器和平台来解决这个问题。

我的问题是:

  1. 通过一些好运,这有可能实际上没有违反C ++标准吗?
  2. 任何人都可以想到使用这样的方案可能遇到的任何现实世界的实际问题吗?
  3. 编辑:@James McNellis问我为什么不能将MyType定义为:

    class MyType {
     public:
      MyType() { mytype_init(this); }
     private:
      mytype t;
    };
    

    原因是我有C回调,它将使用mytype*回调到C ++,我希望能够将其直接转换为MyType*而无需复制。

4 个答案:

答案 0 :(得分:5)

您将mytype*向下转换为MyType*,这是合法的C ++。但是这里存在问题,因为mytype*指针实际上并不指向MyType。它实际上指向mytype。因此,如果你贬低它做一个MyType并尝试访问其成员,它几乎肯定不会工作。即使没有数据成员或virtual函数,您将来也可能使用它,但它仍然是一个巨大的代码味道。

即使它没有违反C ++标准(我认为它也是如此),我仍然会对代码有点怀疑。通常,如果您正在包装C库,那么“现代C ++方式”就是通过RAII idiom

class MyType
{
public:
   // Constructor
    MyType() : myType(::mytype_new()) {}
   // Destructor
   ~MyType() { ::my_type_delete(); /* or something similar to this */ }


   mytype* GetRawMyType() { return myType; }
   const mytype* GetConstRawMyType() const { return myType; }

   void DoSomething(int arg) { ::mytype_dosomething(myType, int arg);  }

private:
    // MyType is not copyable.
    MyType(const MyType&);
    MyType& operator=(const MyType&);

    mytype* myType;
};

// Usage example:
{
    MyType t; // constructor called here
    t.DoSomething(123);   
} // destructor called when scope ends

答案 1 :(得分:3)

我认为拥有mytype*的{​​{1}}数据成员会更加安全和优雅,并在MyType的构造函数中初始化它而不是MyType方法(顺便说一句,如果你想拥有它,必须是静态的)。

答案 2 :(得分:3)

  

通过一些好运,这有可能实际上并没有违反C ++标准吗?

我不是在提倡这种风格,但由于MyTypemytype都是POD,我相信演员表并没有违反标准。我相信MyTypemytype是布局兼容的(2003版,第9.2节,第14节:“如果两个POD-struct ...类型具有相同数量的非静态数据成员,则它们是布局兼容的和相应的非静态数据成员(按顺序)具有布局兼容类型(3.9)。“),因此可以毫无困难地进行转换。

编辑:我不得不测试一下,事实证明我错了。这不是标准的,因为基类使{P}成为非POD。以下内容无法编译:

MyType

Visual C ++提供错误消息“具有基础的类型不是聚合的”。由于“带有基础的类型不是聚合的”,因此我引用的段落根本不适用。

我相信您仍然安全,因为大多数编译器会使#include <cstdio> namespace { extern "C" struct Foo { int i; }; extern "C" int do_foo(Foo* f) { return 5 + f->i; } struct Bar : Foo { int foo_it_up() { return do_foo(this); } }; } int main() { Bar f = { 5 }; std::printf("%d\n", f.foo_it_up()); } 布局与MyType兼容。演员将“工作”,但这不是标准。

答案 3 :(得分:-1)

它确实违反了c ++标准,但它应该适用于大多数(我所知道的)编译器 你在这里依赖于一个特定的实现细节(编译器不关心实际对象是什么,只是你给它的类型),但我认为任何编译器都没有不同的实现细节。一定要在你使用的每个编译器上检查它,它可能会让你措手不及。