如何在 C 中模拟构造函数和析构函数行为(对于特定数据类型)

问题描述 投票:0回答:5

我有一个 C(嵌套)结构,我想在我的代码中自动初始化和销毁它。

我正在 Linux 上使用 GCC (4.4.3) 进行编译。我隐约知道 GCC 函数属性构造函数和析构函数,但它们提供的构造/析构似乎与整个程序相关(即在调用 main() 之前等)。

我希望能够针对不同的数据类型拥有不同的初始化/清理函数 - 这种类似 C++ 的行为是我可以使用 POC 模拟的吗?

我已经包含了 C++ 标签,因为这实际上是我试图在 C 中模拟的 C++ 行为。

c++ c constructor destructor
5个回答
12
投票

没有办法自动执行此操作,至少不能以任何便携式方式执行此操作。在 C 中,您通常会拥有一些类似于构造函数和析构函数的函数 - 它们(取消)分配内存和(取消)初始化字段 - 只不过它们必须显式调用:

typedef struct{} MyStruct;
MyStruct *MyStruct_New(void);
void MyStruct_Free(MyStruct *obj);

该语言根本不是为此而设计的,我认为你不应该试图强迫它。如果你想自动销毁,你不应该使用C。


9
投票

#定义解决问题的方法...

正如之前的作者所指出的,没有自动的方法来完成你所要求的事情,遗憾的是,这很明显,因为 C 没有任何方法来执行真正的 OOP。

但是程序员总是可以通过任何类型的障碍来破解他或她自己。在这篇文章的末尾,我给你写了一个示例黑客来规避这个问题。

提供了一些清理宏的方法,尽管它不那么便携。



- C99 实施

#include <stdio.h>
#include <stdlib.h>

...

#define SCOPIFY(TYPE,NAME, ...) { \
  ctor_ ## TYPE(& NAME); \
  __VA_ARGS__ \
  dtor_ ## TYPE(& NAME); \
} (void)0

...

typedef struct {
  int * p;
} Obj;

void
ctor_Obj(Obj* this) {
   this->p = malloc(sizeof (int));
  *this->p = 123;

  fprintf(stderr, "Obj::ctor, (this -> %p)\n", (void*)this);
}

void
dtor_Obj(Obj* this) {
  free(this->p);

  fprintf(stderr, "Obj::dtor, (this -> %p)\n", (void*)this);
}

...

int
main(int argc, char *argv[])
{
  Obj o1, o2;
    
  SCOPIFY(Obj, o1, 
    fprintf (stderr, "  o1.p -> %d\n", *o1.p);

    SCOPIFY(Obj, o2, 
      int a, b;

      fprintf(stderr, "  o2.p -> %d\n", *o2.p);
      (*o1.p) += (*o2.p);
    );  

    fprintf(stderr, "  o1.p -> %d\n", *o1.p);
  );  

  return 0;
}

输出http://ideone.com/WYrjU

Obj::ctor, (this -> 0xbf8f05ac)
  o1.p -> 123
Obj::ctor, (this -> 0xbf8f05a8)
  o2.p -> 123
Obj::dtor, (this -> 0xbf8f05a8)
  o1.p -> 246
Obj::dtor, (this -> 0xbf8f05ac)

2
投票

从您所写的内容中,我认为您已经知道如何编写 init 和 destroy 函数,这些函数最终递归地将其对应部分用于各个部分。

是的,C 中没有标准机制允许自动构造或销毁之类的事情。

可以通过编写初始化宏来某种程度上替代构造。指定的初始化器可以派上用场

#define TOTO_INITIALIZER(TUTU_PARAM, TATA_PARAM) \
{                                                \
 .tata_member = TATA_INITIALIZER(TATA_PARAM),    \
 .tutu_member = TUTU_INITIALIZER(TUTU_PARAM),    \
}

因为它们使此类代码对于成员重新排序具有鲁棒性。

对于析构函数来说,没有任何东西可以耦合到变量或数据类型。我所知道的唯一可能的是基于范围的资源管理,在 C 中,您可以通过隐藏的

for
范围局部变量来实现。


1
投票

创建结构体时没有默认方法自动调用函数。这是为某种类型的结构设置的创建和初始化函数的示例:

// Simple struct that holds an ID number and a file pointer.
typedef struct
{
    int id;
    FILE *data;
} Datum;

// Function to create a Datum from a given file.
Datum *create_datum(const char *fname)
{
    // Create Datum object.
    Datum *d = (Datum*)malloc(sizeof(Datum));
    // malloc may return NULL if we're out of memory.
    if(d)
    {
        // Initialise ID to something.
        d->id = 0;
        // Open filename passed.
        d->data = fopen(fname, "r");
    }
    return d;
}

// Function to safely destroy a Datum. This function takes a pointer-pointer so
// that it can set the pointer to NULL after deleting the object. Saves you
// from dangling pointers.
void destroy_datum(Datum **dp)
{
    if(!dp)
        return;
    // Get a plain pointer for convenience
    Datum *d = *dp;
    if(d)
    {
        // Close the file.
        fclose(d->data);
        // Delete the object.
        free(d);
        // Set the pointer to NULL.
        *dp = NULL;
    }
}

// Now use these functions:
int main(void)
{
    Datum *datum = create_datum("test.txt");
    if(datum)
    {
        // Do some things!
    }
    destroy_datum(&datum);
    // datum is now equal to NULL.
}

希望有帮助!就像 Homunculus 所说的那样,如果您需要做很多此类事情,那么 C 并不是一种很棒的语言 - 但有时您只是想抽象出创建结构体以及清理结构体的过程。这在模块化设计中特别有用,其中模块可以提供

create_
destroy_
接口函数,并隐藏这些函数的实际实现。


0
投票

我没有看到

gcc
标签,但由于原始海报提到显式使用 GCC 构造函数/析构函数属性:

我想指出的是,还有

cleanup
属性:

cleanup (cleanup_function)
cleanup 属性在以下情况下运行一个函数: 变量超出范围。该属性只能应用于 自动函数范围变量;它可能不适用于参数或 具有静态存储持续时间的变量。该函数必须取一个 参数,指向与变量兼容的类型的指针。这 函数的返回值(如果有)将被忽略。如果 -fExceptions 是 启用,然后在堆栈展开期间运行 cleanup_function 发生在异常处理过程中。注意清理工作 属性不允许捕获异常,只能执行 一种行为。如果 cleanup_function 不存在,会发生什么是未定义的 正常返回。

© www.soinside.com 2019 - 2024. All rights reserved.