有时候,相对于C++方法,使用C方法有哪些优势?

3

最近我一直在思考这个问题。我们大多数人都知道,在C中,为了创建一个结构体,通常需要在引用对象之前加上typedef以避免调用struct关键字。当然,C仅限于使用结构体而不是类。为了弥补这一点,C倾向于使用专用于结构体的全局函数来创建面向对象的方法。

例如:

typedef struct{
    int foo;
    float bar;
    char* baz;
} SomeStruct;

对比。

struct AnotherStruct {
    int foo;
    float bar;
    char* baz;
};

AnotherStruct必须在函数内声明该类型的对象时,加上前缀关键字struct。例如:

int main( ... )
{
   struct AnotherStruct obj1; //correct
   SomeStruct obj2; //correct
   struct SomeStruct obj3; //bad
   AnotherStruct obj4; //bad 
}

从面向对象的角度来看:
typedef struct {
    //member variables here
} SomeStruct;

SomeStruct* SomeStruct_New( int a, int b, int c )
{
    SomeStruct* obj = calloc( sizeof( SomeStruct ), 1 ); //not sure if that's correct -- been a while since I've used calloc.
    obj.a = a;
    obj.b = b;
    obj.c = c;
    return obj;
}

void SomeStruct_Free( SomeStruct* free )
{
    free( free );
}

这些函数很容易在没有包装器的情况下实现 - 我只是举例说明。我的观点是,鉴于您已经可以在C ++中创建一个不需要typedef来声明的结构体,也不需要struct关键字,并使用与这些结构体相关的非封装函数进行面向对象的方法,我很想知道使用C方式在C++中编码是否有任何优势,其中包括将静态全局函数用作私有成员函数,以及返回指向对象的指针的全局函数构造函数。

这仅仅是出于好奇,因为有时我觉得采取C方法只是为了采取它,但这可能只是一种偏好。


1
我不明白你的观点。这不是面向对象的方法,也许这是一种好的编程实践,但结构体中所有成员都是公共的,当然你不能有方法。在C++中,你可以使用类,利用构造函数、析构函数、方法和最重要的多态性! - vulkanino
3
typedef struct Foo Foo 是用来减少敲击量的语法结构,与面向过程编程、面向对象编程、封装与否等问题无关。 - Mankarse
@vulkanino 当在C语言中进行这样的操作时,通常会将成员声明为静态变量,在文件作用域下创建私有封装。这是面向对象的一种方法,即使不太优雅。 - Lundin
@Lundin,当然,但我认为这根本不是面向对象的,它只是尝试封装全局变量,而这些变量可以使用“extern”访问...看不到真正的优势,我认为“工厂”函数所做的:obj.a = a;等等,它混合了创建和初始化... - vulkanino
4个回答

3
很难理解问题的要点。我觉得你的主要问题是:

  • 在某些情况下,
  • "简单"数据+函数

比使用对象更好。

不是。它们是等效的。

除了异常(!)之外,您用C++表达的任何代码都可以用C表达。这只是一种语法糖,使C++对应物更容易阅读。在反对者跟风之前,是的,虚拟表可以在C中模拟。

尽管如此,我仍然宁愿使用C++。编译器检查的封装(private),编译器驱动的重载选择,编译器样板(模板)。这只是语法糖,但是这种甜蜜的糖。

话虽如此:

class Foo {
  Foo() {}

  friend Foo build(int a, int b);
  friend int getA(Foo const& foo);
  friend int getB(Foo const& foo);

  int a;
  int b;
};

可以将其视为面向对象。

编辑多态的简单示例

#include <stdio.h>

// Interface
typedef void (*FunctionPrint)(void const*);

typedef struct {
  FunctionPrint print;
} PrinterInterface;

void print(void const* item, PrinterInterface const* pi) {
  (*pi->print)(item);
}

// Foo
typedef struct { int a; } Foo;

void printFoo(void const* arg) {
  Foo const* foo = (Foo const*)arg;
  printf("Foo{%d}\n", foo->a);
}

PrinterInterface const FooPI = { &printFoo };

// Bar
typedef struct { char* a; } Bar;

void printBar(void const* arg) {
  Bar const* bar = (Bar const*)arg;
  printf("Bar{\"%s\"}\n", bar->a);
}

PrinterInterface const BarPI = { &printBar };

// Main
int main() {
  Foo foo = { 1 };
  Bar bar = { "Hello, World!" };

  print(&foo, &FooPI);
  print(&bar, &BarPI);
}

结果:

Foo{1}
Bar{"Hello, World!"}

我会反驳 :) 你不能以任何方式在C或其他过程式语言中实现多态性。 - vulkanino
@vulkanino:你认为函数指针有什么用途呢? ;) ? - Matthieu M.
@vulkanino:我编辑了我的答案,加入了C语言中的多态。 - Matthieu M.
不行,Matt - 除了可读性差之外 - 当调用print(&foo, &FooPI)时,调用者必须知道(并创建一个特定的)第二个参数类型。这不是类型独立性。尝试编写一个接受“可打印”接口对象的打印函数,并使用异构参数(foo或bar)调用它两次,这是一种可打印类型。在这里你不能有一个像另一个类型一样的行为,即 - 类型独立性。 - vulkanino
@vulkanino:嗯,我不同意 :) 编译器不会自动为您传递指向虚表的指针,因此您必须明确传递它。但是在print函数内部,您对传递的“真实”类型一无所知。当然,您可以通过将对象指针和表指针包装成单个结构体(ala Go)或声明传递的对象的前8个字节始终是指向表的指针并且可以被强制转换(ala C++)来包装它。然而,原则仍然相同。 - Matthieu M.

0
据我所知,这种声明之所以存在,只是因为一些常见的头文件(主要来自操作系统API:比如windows.h或"xlib.h")必须在C和C++程序中使用,而这些程序的版本是不可预测的。
如果这样的API今天被重写(注意:API本身,而不仅仅是接口),它们可能不会有这些声明。 这是一种“混合编程”,可以让API开发人员确保内存映射(当结构体绑定到硬件或外部二进制格式时很重要)和“魔术数字定义”,这些定义不会在不同的语言的不同头文件中重复出现。

0

我不认为在C++中有任何情况是有意义的。

最明显的情况是中断和线程回调函数,这时你会想要使用C语言的方法。但是,在C++中可以将它们编写为私有静态成员,这是更好的选择。

通常情况下,在C或C++中甚至没有必要从函数中返回指针。你的例子并不是很好的面向对象编程,因为你强制用户在不需要的情况下使用动态分配。当在C中编写面向对象代码时,通常将分配留给调用者。

在实时嵌入式编程中,我可以想到一些情况,你不希望调用任何构造函数,因此完全避免使用类,但在这种情况下,你可能根本不会使用C++。


0

最近,我开始使用Horde3D,它实现了(或多或少)你所描述的内容。内部采用C++实现,在C中暴露了一个简单的API。

这种方法使得像我这样想要通过外部语言接口重复使用引擎的人们感到吸引。我正在将引擎与SWI-Prolog耦合,由于Prolog不遵循OOP方向,因此使用(例如)OGRE接口没有任何好处。OGRE具有更多功能,但简单性也有其优点...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接