这样做是否有副作用:
C代码:
struct foo {
int k;
};
int ret_foo(const struct foo* f){
return f.k;
}
C ++代码:
class bar : public foo {
int my_bar() {
return ret_foo( (foo)this );
}
};
C ++代码周围有extern "C"
,每个代码都在自己的编译单元内。
这可以跨编译器移植吗?
答案 0 :(得分:28)
这完全合法。在C ++中,类和结构是相同的概念,但默认情况下所有结构成员都是公共的。这是唯一的区别。因此,询问是否可以扩展结构与询问是否可以扩展类没有什么不同。
这里有一点需要注意。从编译器到编译器的布局一致性无保证。因此,如果使用与C ++代码不同的编译器编译C代码,则可能会遇到与成员布局相关的问题(特别是填充)。使用来自同一供应商的C和C ++编译器时甚至会发生这种情况。
我已经用gcc和g ++发生了这种情况。我参与了一个使用了几个大型结构的项目。不幸的是,g ++打包的结构明显比gcc松散,这导致在C和C ++代码之间共享对象时出现重大问题。我们最终不得不手动设置打包和插入填充,以使C和C ++代码对结构进行相同的处理。但请注意,无论子类化如何,都可能出现此问题。事实上,在这种情况下,我们并没有将C结构子类化。
答案 1 :(得分:11)
我当然不建议使用这种奇怪的子类。最好将设计更改为使用合成而不是继承。 只需成为一名成员
foo * m_pfoo;
在bar类中,它将完成同样的工作。
你可以做的另一件事是再创建一个类FooWrapper,其中包含相应的getter方法的结构。然后你可以继承包装器。这样,虚析构函数的问题就消失了。
答案 2 :(得分:3)
“永远不要来自具体的课程。” - Sutter
“使非叶类抽象化。” - 迈耶斯
将非接口类子类化是完全错误的。你应该重构你的库。
从技术上讲,你可以做你想要的,只要你不调用未定义的行为,例如:例如,通过指向其基类子对象的指针删除指向派生类的指针。您甚至不需要extern "C"
来获取C ++代码。是的,它是便携式的。但它的设计很差。
答案 3 :(得分:3)
这是完全合法的,但它可能会让其他程序员感到困惑。
您可以使用继承来使用方法和构造函数扩展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.
};
扩展结构可能“更”邪恶,但不是你禁止做的事情。
答案 4 :(得分:2)
这可以跨编译器移植吗?
绝对不是。请考虑以下事项:
foo* x = new bar();
delete x;
为了使这个工作,foo的析构函数必须是虚拟的,但它显然不是。只要您不使用new
并且只要派生的对象没有自定义析构函数,您就可以幸运。
/编辑:另一方面,如果代码仅用于问题,则继承没有优势。只需遵循m_pGladiator提供的建议。
答案 5 :(得分:2)
这是完全合法的,你可以在MFC CRect和CPoint类的实践中看到它。 CPoint派生自POINT(在windef.h中定义),CRect派生自RECT。您只是使用成员函数装饰对象。只要您不使用更多数据扩展对象,就可以了。实际上,如果你有一个复杂的C结构,这对于默认初始化很困难,那么使用包含默认构造函数的类扩展它是一种处理该问题的简单方法。
即使你这样做:
foo *pFoo = new bar;
delete pFoo;
然后你很好,因为你的构造函数和析构函数很简单,你还没有分配任何额外的内存。
您也不必用'extern“C”'包装C ++对象,因为您实际上并没有将C ++ 类型传递给C函数。
答案 6 :(得分:1)
我认为这不一定是个问题。行为定义得很好,只要你小心处理生命周期问题(不要混合和匹配C ++和C代码之间的分配)就可以做你想做的事情。它应该在编译器之间完全可移植。
析构函数的问题是真实的,但是在基类析构函数不是虚拟的任何时候都适用于C结构。这是你需要注意的事情,但不排除使用这种模式。
答案 7 :(得分:1)
它可以工作,并且可以移植但是你不能使用任何虚函数(包括析构函数)。
我建议您不要这样做,而是让Bar包含一个Foo。
class Bar
{
private:
Foo mFoo;
};
答案 8 :(得分:0)
我不明白为什么你不简单地让ret_foo成为一个成员方法。您当前的方式使您的代码非常难以理解。使用成员变量和get / set方法首先使用真正的类有什么困难?
我知道可以在C ++中对结构进行子类化,但危险在于其他人无法理解你编码的内容,因为实际上很少有人这样做。我会选择一个强大而通用的解决方案。
答案 9 :(得分:0)
它可能会起作用,但我不相信它是有保证的。以下是ISO C ++ 10/5的引用:
基类子对象的布局(3.7)可能与同一类型的派生对象的布局不同。
很难看出在“现实世界”中实际情况如何。
编辑:
底线是标准没有限制基类子对象布局可以与具有相同Base类型的具体对象不同的位置数。结果是您可能具有的任何假设(例如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_;
};