是否可以在C ++中继承C结构并在C代码中使用指向该结构的指针?

时间:2020-03-06 14:39:48  来源:igfitidea点击:

这样做有副作用吗?

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"`,每个代码都在其自己的编译单元内。

这可以跨编译器移植吗?

解决方案

哇,这很邪恶。

Is this portable across compilers?

绝对不是。考虑以下:

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

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

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

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

foo* m_pfoo;

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

我们可以做的另一件事是再制作一个FooWrapper类,该类本身包含具有相应getter方法的结构。然后,我们可以对包装器进行子类化。这样,虚拟析构函数的问题就解决了。

我认为这不一定是问题。行为定义明确,只要我们谨慎对待生命周期问题(不要在C ++和C代码之间混合和匹配分配)就可以完成我们想要的事情。它应该可以在所有编译器之间完美移植。

析构函数的问题是真实的,但只要基类析构函数不是虚拟的,就适用于C结构。我们需要注意这一点,但并不排除使用此模式。

它将起作用,并且可移植,但是我们不能使用任何虚拟函数(包括析构函数)。

我建议我们不要让Bar包含Foo。

class Bar
{
private:
   Foo  mFoo;
};

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

我知道可以用C ++继承结构,但是危险是其他人将无法理解我们编写的代码,因为很少有人真正做到这一点。我会选择一个健壮且通用的解决方案。

它可能会起作用,但我不保证一定会。以下是ISO C ++ 10/5的引文:

A base class subobject might have a layout (3.7) different from the layout of a most derived object of the same type.

很难看到在"现实世界"中实际上是怎么回事。

编辑:

最重要的是,该标准并没有限制基类子对象布局可以与具有相同基类型的具体对象不同的位置数量。结果是,我们可能具有的任何假设(例如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_;
};

“Never derive from concrete classes.” — Sutter
  
  “Make non-leaf classes abstract.” — Meyers

子类化非接口类根本是错误的。我们应该重构库。

从技术上讲,只要我们不通过以下方法调用未定义的行为,就可以执行所需的操作。 g。通过指向其基类子对象的指针删除指向该派生类的指针。我们甚至不需要extern" C"来获取C ++代码。是的,它的便携性。但是它的设计很差。

这是完全合法的,尽管这可能会使其他程序员感到困惑。

我们可以使用继承来扩展带有方法和构造函数的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.
};

扩展结构可能是"更多"的邪恶,但这不是我们禁止做的事情。

这是完全合法的,我们可以在MFC CRect和CPoint类的实践中看到它。 CPoint源自POINT(在windef.h中定义),而CRect源自RECT。我们只是用成员函数装饰对象。只要我们不使用更多数据扩展对象,就可以了。实际上,如果我们有一个复杂的C结构,很难进行默认初始化,则使用包含默认构造函数的类对其进行扩展是解决该问题的简便方法。

即使我们这样做:

foo *pFoo = new bar;
delete pFoo;

那么就很好了,因为构造函数和析构函数都很简单,并且我们没有分配任何额外的内存。

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

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

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

我曾经在gcc和g ++中发生过这种情况。我参与了一个使用多个大型结构的项目。不幸的是,g ++打包的结构比gcc宽松得多,这在C和C ++代码之间共享对象时引起了严重的问题。我们最终不得不手动设置打包并插入填充,以使C和C ++代码将结构相同。但是请注意,不管子类化如何,都可能发生此问题。实际上,在这种情况下,我们没有将C结构子类化。