Подтвердить что ты не робот

Непрозрачные C-структуры: как их объявить?

Я видел два следующих стиля, объявляющих непрозрачные типы в C API. Есть ли явное преимущество в использовании одного стиля над другим?

Вариант 1

// foo.h
typedef struct foo * fooRef;
void doStuff(fooRef f);

// foo.c
struct foo {
    int x;
    int y;
};

Вариант 2

// foo.h
typedef struct _foo foo;
void doStuff(foo *f);

// foo.c
struct _foo {
    int x;
    int y;
};
4b9b3361

Ответ 1

Мое голосование за третий вариант, который mouviciel отправил, затем удалил:

Я видел третий способ:

// foo.h
struct foo;
void doStuff(struct foo *f);

// foo.c
struct foo {
    int x;
    int y;
};

Если вы действительно не можете набирать ключевое слово struct, то typedef struct foo foo; (примечание: избавиться от бесполезного и проблематичного подчеркивания) приемлемо. Но что бы вы ни делали, никогда используйте typedef для определения имен типов указателей. Он скрывает чрезвычайно важную информацию о том, что переменные этого типа ссылаются на объект, который может быть изменен всякий раз, когда вы передаете их функциям, и он обрабатывает разные квалификационные (например, const -квалифицированные) версии указателя a большая боль.

Ответ 2

bar(const fooRef) объявляет неизменяемый адрес в качестве аргумента. bar(const foo *) объявляет адрес непреложного аргумента foo как.

По этой причине я предпочитаю вариант 2. I.e., представленный тип интерфейса - это тот, где cv-ness можно указать на каждом уровне косвенности. Конечно, можно обойти библиотеку библиотеки 1-го варианта и просто использовать foo, открывая себя для всех видов ужасов, когда редактор библиотеки меняет реализацию. (То есть, писатель библиотеки 1-й версии только воспринимает, что fooRef является частью интерфейса инварианта и что foo может прийти, пойти, быть изменен, что угодно. Писатель библиотеки 2-й версии воспринимает, что foo является частью инварианта интерфейс.)

Я больше удивляюсь, что никто не предложил комбинированные конструкции typedef/struct.
typedef struct { ... } foo;

Ответ 3

Вариант 1.5

Я привык использовать Вариант 1, за исключением случаев, когда вы называете свою ссылку с помощью _h чтобы обозначить, что она является "дескриптором" "объекта" стиля C данного "класса" C. Затем вы гарантируете, что ваши прототипы функций используют const везде, где содержимое "дескриптора" этого объекта является только вводом, и не может быть изменено, и не используете const везде, где содержимое может быть изменено.

Вот полный пример:

//======================================================================================================================
// my_module.h
//======================================================================================================================

// An opaque pointer (handle) to a C-style "object" of "class" type "my_module" (struct my_module_s *, or my_module_h):
typedef struct my_module_s *my_module_h;

// Create a new "object" of "class" "my_module":
// A function that takes a *pointer to* an "object" handle, 'malloc memory for a new copy of the opaque 
// 'struct my_module_s', then points the user input handle (via its passed-in pointer) to this newly-created 
// "object" of "class" "my_module".
void my_module_open(my_module_h * my_module_h_p);

// A function that takes this "object" (via its handle) as an input only and cannot modify it
void my_module_do_stuff1(const my_module_h my_module);

// A function that can modify the private content of this "object" (via its handle) (but still cannot modify the 
// handle itself)
void my_module_do_stuff2(my_module_h my_module);

// Destroy the passed-in "object" of "class" type "my_module":
// A function that can close this object by stopping all operations, as required, and 'free'ing its memory.
// 'struct my_module_s', then points the user input handle (via its passed-in pointer) to this newly-created "object".
void my_module_close(my_module_h my_module);

//======================================================================================================================
// my_module.c
//======================================================================================================================

// Definition of the opaque struct "object" of C-style "class" "my_module".
// - NB: Since this is an opaque struct (declared in the header but not defined until the source file), it has the 
// following 2 important properties:
// 1) It permits data hiding, wherein you end up with the equivalent of a C++ "class" with only *private* member 
// variables.
// 2) Objects of this "class" can only be dynamically allocated. No static allocation is possible since any module
// including the header file does not know the contents of *nor the size of* (this is the critical part) this "class"
// (ie: C struct).
struct my_module_s
{
    int my_private_int1;
    int my_private_int2;
    float my_private_float;
    // etc. etc--add more "private" member variables as you see fit
}

void my_module_open(my_module_h * my_module_h_p)
{
    // Ensure the passed-in pointer is not NULL (since it is a core dump/segmentation fault to try to dereference 
    // a NULL pointer)
    if (!my_module_h_p)
    {
        // Print some error or store some error code here, and return it at the end of the function instead of 
        // returning void.
        goto done;
    }

    // Now allocate the actual memory for a new my_module C object from the heap, thereby dynamically creating this
    // C-style "object".
    my_module_h my_module; // Create a local object handle (pointer to a struct)
    my_module = malloc(sizeof(*my_module)); // Dynamically allocate memory for the full contents of the struct "object"
    if (!my_module) 
    {
        // Malloc failed due to out-of-memory. Print some error or store some error code here, and return it
        // at the end of the function instead of returning void.
        goto done;
    }

    // Initialize all memory to zero (OR just use 'calloc()' instead of 'malloc()' above!)
    memset(my_module, 0, sizeof(*my_module));

    // Now pass out this object to the user, and exit.
    *my_module_h_p = my_module;

done:
}

void my_module_do_stuff1(const my_module_h my_module)
{
    // Ensure my_module is not a NULL pointer.
    if (!my_module)
    {
        goto done;
    }

    // Do stuff where you use my_module private "member" variables.
    // Ex: use 'my_module->my_private_int1' here, or 'my_module->my_private_float', etc. 

done:
}

void my_module_do_stuff2(my_module_h my_module)
{
    // Ensure my_module is not a NULL pointer.
    if (!my_module)
    {
        goto done;
    }

    // Do stuff where you use AND UPDATE my_module private "member" variables.
    // Ex:
    my_module->my_private_int1 = 7;
    my_module->my_private_float = 3.14159;
    // Etc.

done:
}

void my_module_close(my_module_h my_module)
{
    // Ensure my_module is not a NULL pointer.
    if (!my_module)
    {
        goto done;
    }

    free(my_module);

done:
}

Единственными улучшениями после этого было бы:

  1. Реализовать полную обработку ошибок и вернуть ошибку вместо void.
  2. Добавьте структуру конфигурации my_module_config_t в файл .h и передайте ее в функцию open для обновления внутренних переменных при создании нового объекта. Пример:

    //--------------------
    // my_module.h
    //--------------------
    
    // my_module configuration struct
    typedef struct my_module_config_s
    {
        int my_config_param_int;
        int my_config_param_float;
    } my_module_config_t;
    
    void my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config);
    
    //--------------------
    // my_module.c
    //--------------------
    
    void my_module_open(my_module_h * my_module_h_p, const my_module_config_t *config)
    {
        // Ensure the passed-in pointer is not NULL (since it is a core dump/segmentation fault to try to dereference 
        // a NULL pointer)
        if (!my_module_h_p)
        {
            // Print some error or store some error code here, and return it at the end of the function instead of 
            // returning void.
            goto done;
        }
    
        // Now allocate the actual memory for a new my_module C object from the heap, thereby dynamically creating this
        // C-style "object".
        my_module_h my_module; // Create a local object handle (pointer to a struct)
        my_module = malloc(sizeof(*my_module)); // Dynamically allocate memory for the full contents of the struct "object"
        if (!my_module) 
        {
            // Malloc failed due to out-of-memory. Print some error or store some error code here, and return it
            // at the end of the function instead of returning void.
            goto done;
        }
    
        // Initialize all memory to zero (OR just use 'calloc()' instead of 'malloc()' above!)
        memset(my_module, 0, sizeof(*my_module));
    
        // Now initialize the object with values per the config struct passed in.
        my_module->my_private_int1 = config->my_config_param_int;
        my_module->my_private_int2 = config->my_config_param_int*3/2;
        my_module->my_private_float = config->my_config_param_float;        
        // etc etc
    
        // Now pass out this object to the user, and exit.
        *my_module_h_p = my_module;
    
    done:
    }