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

发布于 2024-07-06 05:12:21 字数 360 浏览 5 评论 0原文

这样做是否有副作用:

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 there a side effect in doing this:

C code:

struct foo {
      int k;
};

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

C++ code:

class bar : public foo {

   int my_bar() { 
       return ret_foo( (foo)this ); 
   }

};

There's an extern "C" around the C++ code and each code is inside its own compilation unit.

Is this portable across compilers?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(10

雨的味道风的声音 2024-07-13 05:12:22

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

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

我在 gcc 和 g++ 中遇到过这种情况。 我从事的项目使用了几个大型结构。 不幸的是,g++ 的结构打包比 gcc 松散得多,这导致在 C 和 C++ 代码之间共享对象时出现严重问题。 我们最终不得不手动设置打包和插入填充,以使 C 和 C++ 代码以相同的方式对待结构。 但请注意,无论子类化如何,都可能会发生此问题。 事实上,在本例中我们并没有对 C 结构体进行子类化。

This is entirely legal. In C++, classes and structs are identical concepts, with the exception that all struct members are public by default. That's the only difference. So asking whether you can extend a struct is no different than asking if you can extend a class.

There is one caveat here. There is no guarantee of layout consistency from compiler to compiler. So if you compile your C code with a different compiler than your C++ code, you may run into problems related to member layout (padding especially). This can even occur when using C and C++ compilers from the same vendor.

I have had this happen with gcc and g++. I worked on a project which used several large structs. Unfortunately, g++ packed the structs significantly looser than gcc, which caused significant problems sharing objects between C and C++ code. We eventually had to manually set packing and insert padding to make the C and C++ code treat the structs the same. Note however, that this problem can occur regardless of subclassing. In fact we weren't subclassing the C struct in this case.

雨后彩虹 2024-07-13 05:12:22

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

您可以使用继承来通过方法和构造函数扩展 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.
};

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

This is perfectly legal, though it might be confusing for other programmers.

You can use inheritance to extend C-structs with methods and constructors.

Sample :

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

Extending structs might be "more" evil, but is not something you are forbidden to do.

叫思念不要吵 2024-07-13 05:12:22

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

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

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

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

“Never derive from concrete classes.” — Sutter

“Make non-leaf classes abstract.” — Meyers

It’s simply wrong to subclass non-interface classes. You should refactor your libraries.

Technically, you can do what you want, so long as you don’t invoke undefined behavior by, e. g., deleting a pointer to the derived class by a pointer to its base class subobject. You don’t even need extern "C" for the C++ code. Yes, it’s portable. But it’s poor design.

微暖i 2024-07-13 05:12:22

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

foo* m_pfoo;

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

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

I certainly not recommend using such weird subclassing. It would be better to change your design to use composition instead of inheritance.
Just make one member

foo* m_pfoo;

in the bar class and it will do the same job.

Other thing you can do is to make one more class FooWrapper, containing the structure in itself with the corresponding getter method. Then you can subclass the wrapper. This way the problem with the virtual destructor is gone.

百思不得你姐 2024-07-13 05:12:22

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

即使您这样做:

foo *pFoo = new bar;
delete pFoo;

那么您也可以,因为您的构造函数和析构函数很简单,并且您没有分配任何额外的内存。

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

This is perfectly legal, and you can see it in practice with the MFC CRect and CPoint classes. CPoint derives from POINT (defined in windef.h), and CRect derives from RECT. You are simply decorating an object with member functions. As long as you don't extend the object with more data, you're fine. In fact, if you have a complex C struct that is a pain to default-initialize, extending it with a class that contains a default constructor is an easy way to deal with that issue.

Even if you do this:

foo *pFoo = new bar;
delete pFoo;

then you're fine, since your constructor and destructor are trivial, and you haven't allocated any extra memory.

You also don't have to wrap your C++ object with 'extern "C"', since you're not actually passing a C++ type to the C functions.

肩上的翅膀 2024-07-13 05:12:22

哇,这太邪恶了。

这可以跨编译器移植吗?

绝对不是。 请考虑以下事项:

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

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

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

Wow, that's evil.

Is this portable across compilers?

Most definitely not. Consider the following:

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

In order for this to work, foo's destructor must be virtual which it clearly isn't. As long as you don't use new and as long as the derived objectd don't have custom destructors, though, you could be lucky.

/EDIT: On the other hand, if the code is only used as in the question, inheritance has no advantage over composition. Just follow the advice given by m_pGladiator.

爱给你人给你 2024-07-13 05:12:22

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

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

class Bar
{
private:
   Foo  mFoo;
};

It will work, and portably BUT you cannot use any virtual functions (which includes destructors).

I would recommend that instead of doing this you have Bar contain a Foo.

class Bar
{
private:
   Foo  mFoo;
};
若水般的淡然安静女子 2024-07-13 05:12:22

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

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

I don't think it is necessarily a problem. The behaviour is well defined, and as long as you are careful with life-time issues (don't mix and match allocations between the C++ and C code) will do what you want. It should be perfectly portable across compilers.

The problem with destructors is real, but applies any time the base class destructor isn't virtual not just for C structs. It is something you need to be aware of but doesn't preclude using this pattern.

爱给你人给你 2024-07-13 05:12:22

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

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

I don't get why you don't simply make ret_foo a member method. Your current way makes your code awfully hard to understand. What is so difficult about using a real class in the first place with a member variable and get/set methods?

I know it's possible to subclass structs in C++, but the danger is that others won't be able to understand what you coded because it's so seldom that somebody actually does it. I'd go for a robust and common solution instead.

深者入戏 2024-07-13 05:12:22

它可能会起作用,但我不相信它一定会起作用。 以下是 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_;
};

It probably will work but I do not believe it is guaranteed to. The following is a quote from 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.

It's hard to see how in the "real world" this could actually be the case.

EDIT:

The bottom line is that the standard has not limited the number of places where a base class subobject layout can be different from a concrete object with that same Base type. The result is that any assumptions you may have, such as POD-ness etc. are not necessarily true for the base class subobject.

EDIT:

An alternative approach, and one whose behaviour is well defined is to make 'foo' a member of 'bar' and to provide a conversion operator where it's necessary.

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_;
};
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文