24

这样做是否有副作用:

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 ); 
   }

};

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

这可以跨编译器移植吗?

4

10 回答 10

30

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

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

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

于 2008-09-24T16:30:06.163 回答
11

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

富* m_pfoo;

在酒吧课上,它会做同样的工作。

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

于 2008-09-24T14:04:18.683 回答
3

“永远不要从具体类派生。” — 萨特

“使非叶类抽象。” — 迈耶斯

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

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

于 2008-09-24T14:21:38.970 回答
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.
};

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

于 2008-09-24T14:31:06.340 回答
2

哇,太邪恶了

这可以跨编译器移植吗?

绝对不是。考虑以下:

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

为了使它工作,foo 的析构函数必须是虚拟的,而它显然不是。但是,只要您不使用new并且只要派生对象没有自定义析构函数,您就会很幸运。

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

于 2008-09-24T14:00:55.057 回答
2

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

即使你这样做:

foo *pFoo = new bar;
delete pFoo;

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

您也不必用'extern "C"' 包装您的C++ 对象,因为您实际上并没有将C++类型传递给C 函数。

于 2008-09-24T15:43:36.400 回答
1

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

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

于 2008-09-24T14:07:49.750 回答
1

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

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

class Bar
{
private:
   Foo  mFoo;
};
于 2008-09-24T14:08:50.717 回答
0

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

我知道可以在 C++ 中对结构进行子类化,但危险在于其他人将无法理解您编写的代码,因为很少有人真正做到这一点。我会选择一个强大且通用的解决方案。

于 2008-09-24T14:10:12.850 回答
0

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

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

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

编辑:

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

编辑:

另一种方法,其行为被明确定义的方法是使 '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_;
};
于 2008-09-24T14:20:14.510 回答