it-swarm.com.de

Opake C-Strukturen: Wie sollen sie deklariert werden?

Ich habe die beiden folgenden Arten der Deklaration undurchsichtiger Typen in C-APIs gesehen. Gibt es einen klaren Vorteil bei der Verwendung eines Stils gegenüber dem anderen?

Option 1

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

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

Option 2

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

// foo.c
struct _foo {
    int x;
    int y;
};
40
splicer

Ich stimme für die dritte Option, die von mouviciel gepostet und dann gelöscht wurde:

Ich habe einen dritten Weg gesehen:

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

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

Wenn Sie das struct-Schlüsselwort wirklich nicht ertragen können, ist typedef struct foo foo; (Hinweis: Entfernen Sie den nutzlosen und problematischen Unterstrich) akzeptabel. Was auch immer Sie tun: niemals Verwenden Sie typedef, um Namen für Zeigertypen zu definieren. Es verbirgt die äußerst wichtige Information, die von Variablen dieses Typs auf ein Objekt verweist, das geändert werden kann, wenn Sie sie an Funktionen übergeben, und es macht den Umgang mit unterschiedlich qualifizierten (zum Beispiel const- qualifizierten) Versionen des Zeigers zu einem großen Schmerz .

65
R..

bar(const fooRef) deklariert eine unveränderliche Adresse als Argument. bar(const foo *) deklariert eine Adresse eines unveränderlichen foo als Argument.

Aus diesem Grund neige ich dazu, die Option 2 zu bevorzugen. Das heißt, der dargestellte Schnittstellentyp ist einer, bei dem cv-ness auf jeder Ebene der Dereferenzierung angegeben werden kann. Natürlich kann man can dem Bibliotheksschreiber von Option 1 ausweichen und einfach foo verwenden, um sich allen Arten von Horror zu öffnen, wenn der Bibliotheksschreiber die Implementierung ändert. (Das heißt, der Bibliotheksautor der Option 1 erkennt nur, dass fooRef Teil der invarianten Schnittstelle ist und dass foo kommen, gehen, geändert werden kann, was auch immer. Der Bibliotheksautor der Option 2 erkennt, dass foo Teil der invarianten Schnittstelle ist.)

Ich bin eher überrascht, dass niemand kombinierte Typedef/Struktur-Konstruktionen vorgeschlagen hat.
typedef struct { ... } foo;

1
Eric Towers

Option 1.5

Ich bin es gewohnt, Option 1 zu verwenden, es sei denn, Sie benennen Ihre Referenz mit _h, um anzuzeigen, dass es sich um ein "Handle" für ein "Objekt" im C-Stil dieser gegebenen "Klasse" handelt. Dann stellen Sie sicher, dass Ihre Funktionsprototypen const verwenden, wenn der Inhalt dieses Objekts "handle" nur eine Eingabe ist und nicht geändert werden kann. Verwenden Sie const nicht, wenn der Inhalt can geändert werden kann.

Hier ist ein vollständiges Beispiel:

//======================================================================================================================
// 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`s memory for a new copy of the opaque 
// `struct my_module_s`, then points the user's 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's 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:
}

Die einzigen Verbesserungen darüber hinaus wären:

  1. Implementieren Sie die vollständige Fehlerbehandlung und geben Sie den Fehler anstelle von void zurück.
  2. Fügen Sie der .h-Datei eine Konfigurationsstruktur mit dem Namen my_module_config_t hinzu, und übergeben Sie sie an die Funktion open, um die internen Variablen zu aktualisieren, wenn Sie ein neues Objekt erstellen. Beispiel:

    //--------------------
    // 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:
    }
    
0
Gabriel Staples