是否可以在C ++中子类化C结构并在C代码中使用指向结构的指针?

这样做是否有副作用:

C代码:

struct foo { int k; }; int ret_foo(const struct foo* f){ return fk; } 

C ++代码:

 class bar : public foo { int my_bar() { return ret_foo( (foo)this ); } }; 

C ++代码周围有一个extern "C" ,每个代码都在自己的编译单元中。

这可以跨编译器移植吗?

这完全合法。 在C ++中,类和结构是相同的概念,但默认情况下所有结构成员都是公共的。 这是唯一的区别。 因此,询问是否可以扩展结构与询问是否可以扩展类没有什么不同。

这里有一个警告。 从编译器到编译器无法保证布局一致性。 因此,如果使用与C ++代码不同的编译器编译C代码,则可能会遇到与成员布局相关的问题(特别是填充)。 使用来自同一供应商的C和C ++编译器时甚至会发生这种情况。

我用gcc和g ++发生过这种情况。 我参与了一个使用了几个大型结构的项目。 不幸的是,g ++打包的结构明显比gcc松散,这导致在C和C ++代码之间共享对象时出现重大问题。 我们最终不得不手动设置打包和插入填充,以使C和C ++代码对结构进行相同的处理。 但请注意,无论子类化如何,都可能出现此问题。 事实上,在这种情况下,我们不是C语言结构的子类。

我当然不建议使用这种奇怪的子类。 最好将设计更改为使用合成而不是inheritance。 只做一个成员

foo * m_pfoo;

在酒吧class,它将做同样的工作。

您可以做的另一件事是再创建一个类FooWrapper,其中包含相应的getter方法的结构。 然后你可以inheritance包装器。 这样,虚拟析构函数的问题就消失了。

“永远不要来自具体的课程。” – 萨特

“让非叶类抽象化。” – 迈耶斯

将非接口类子类化是完全错误的。 你应该重构你的库。

从技术上讲,只要不调用未定义的行为,例如通过指向其基类子对象的指针删除指向派生类的指针,就可以执行所需的操作。 你甚至不需要extern "C"代表C ++代码。 是的,它是便携式的。 但这是糟糕的设计。

这是完全合法的,但它可能会让其他程序员感到困惑。

您可以使用inheritance来使用方法和构造函数扩展C结构。

样品:

 struct POINT { int x, y; } class CPoint : POINT { public: CPoint( int x_, int y_ ) { x = x_; y = y_; } const CPoint& operator+=( const POINT& op2 ) { x += op2.x; y += op2.y; return *this; } // etc. }; 

扩展结构可能“更”邪恶,但不是你禁止做的事情。

哇,这是邪恶的。

这可以跨编译器移植吗?

绝对不是。 考虑以下:

 foo* x = new bar(); delete x; 

为了使它能够工作,foo的析构函数必须是虚拟的,它显然不是虚拟的。 只要你不使用new ,只要派生的objectd没有自定义析构函数,你就可以幸运了。

/编辑:另一方面,如果代码仅用于问题中,则inheritance没有优于组合的优势。 只需遵循m_pGladiator提供的建议即可。

这是完全合法的,你可以在MFC CRect和CPoint类的实践中看到它。 CPoint派生自POINT(在windef.h中定义),CRect派生自RECT。 您只是使用成员函数装饰对象。 只要您不使用更多数据扩展对象,就可以了。 实际上,如果你有一个复杂的C结构,这对于默认初始化很困难,那么使用包含默认构造函数的类扩展它是一种处理该问题的简单方法。

即使你这样做:

 foo *pFoo = new bar; delete pFoo; 

那么你很好,因为你的构造函数和析构函数是微不足道的,你没有分配任何额外的内存。

您也不必使用’extern“C”’包装C ++对象,因为您实际上并未将C ++ 类型传递给C函数。

我认为这不一定是个问题。 行为是明确定义的,只要你小心处理生命周期问题(不要混合和匹配C ++和C代码之间的分配)就会做你想要的。 它应该在编译器之间完全可移植。

析构函数的问题是真实的,但是在基类析构函数不是虚拟的任何时候都适用于C结构。 这是您需要注意的事项,但不排除使用此模式。

它可以工作,并且可移植但是你不能使用任何虚函数(包括析构函数)。

我建议你不要这样做,而是让Bar包含一个Foo。

 class Bar { private: Foo mFoo; }; 

我不明白你为什么不简单地让ret_foo成为一个成员方法。 您当前的方式使您的代码非常难以理解。 使用成员变量和get / set方法首先使用真正的类有什么困难?

我知道可以在C ++中对结构进行子类化,但危险在于其他人无法理解你编码的内容,因为实际上很少有人能够理解它。 我会选择一个强大而通用的解决方案。

它可能会工作,但我不相信它是保证。 以下是ISO C ++ 10/5的引用:

基类子对象可能具有与同一类型的最派生对象的布局不同的布局(3.7)。

很难看出在“现实世界”中实际情况如何。

编辑:

底线是标准没有限制基类子对象布局可以与具有相同基类型的具体对象不同的位置数。 结果是您可能具有的任何假设(例如POD-ness等)对于基类子对象不一定是正确的。

编辑:

另一种方法,其行为定义明确,是使’foo’成为’bar’的成员,并在必要时提供转换运算符。

 class bar { public: int my_bar() { return ret_foo( foo_ ); } // // This allows a 'bar' to be used where a 'foo' is expected inline operator foo& () { return foo_; } private: foo foo_; };