2009-02-11 4 views
23

Gibt es eine Möglichkeit, eine Art von Standardkonstruktor (wie ein C++ - Konstruktor) für C-Benutzertypen zu haben, die mit einer Struktur definiert sind?Standardkonstruktor in C

Ich habe bereits ein Makro, das wie ein schneller Initialisierer funktioniert (wie der für pthread_mutex), aber ich wollte wissen, ob Sie zufällig einige (oder alle) Felder einer Struktur bei der Deklaration gefüllt haben können.

Zum Beispiel mit dem pthread_mutex Beispiel würde Ich mag

pthread_mutex_t my_mutex; 

die gleiche Wirkung wie

pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER; 

Antwort

32

Sie können Initialisierungsfunktionen erstellen, die einen Zeiger auf eine Struktur verwenden. Dies war gängige Praxis.

Auch Funktionen, die eine Struktur erstellen und initialisieren (wie eine Factory) - also gibt es nie eine Zeit, in der die Struktur im Client-Code "nicht initialisiert" ist. Natürlich - die Menschen folgen der Konvention und verwenden Sie den „Konstruktor“/Fabrik ...

schrecklich Pseudo-Code mit NO Fehlerprüfung auf malloc oder kostenlos

somestruct* somestruct_factory(/* per haps some initializer agrs? */) 
{ 
    malloc some stuff 
    fill in some stuff 
    return pointer to malloced stuff 
} 


void somestruct_destructor(somestruct*) 
{ 
    do cleanup stuff and also free pointer 
    free(somestruct); 
} 

Jemand kommen wird wahrscheinlich zusammen und erklären, wie man annimmt, einige frühe C++ - Preprozessoren/Compiler arbeiteten, um dies alles in C zu tun.

+3

Oh cfront, wie wir dich vermissen. – joeforker

+5

... wie ein schrecklicher brennender Ausschlag. – Randolpho

+0

Und wie behandelt cfront Konstruktor am Anfang? – claf

8

Nein, nicht direkt zu haben. Die nächste Möglichkeit besteht darin, eine Funktion zu schreiben, die eine Instanz zuweist und einige der Felder ausfüllt.

4

Sie können eine Funktion schreiben, die die C-Struktur zurückgibt:

struct file create_file(int i, float f) { 
    struct file obj = { i, f }; 
    // other code here... 
    return obj; 
} 

Wenn Sie, ob Sie sich fragen, „normal“ Member-Funktionen in C. Nun haben kann, Sie zu einem gewissen Grad kann. Ich bevorzuge den Objekt-als-Erst-Argument-Stil. Sie übergeben einen Zeiger auf Ihre Struktur als erstes Argument. Auf diese Weise können Sie mehrere Funktionen haben, die Definition die Schnittstelle, um Ihre Objekte:

int file_get_integer(struct file *self) { return self->i; } 
float file_get_float(struct file *self) { return self->f; } 

Wenn Sie in diesem Stil schreiben, was Sie am Ende haben ist ein abstrakter Datentyp. Ich habe Leute gesehen, die Mitglied Funktionsaufruf Syntax in C++ Emulation von Funktionszeigern in ihre Struktur aufweisen, und dann tun:

obj.get_integer(&obj); 

Es wird von dem Linux-Kernel verwendet wird die Schnittstelle zur Definition von Systemtreibern einzureichen. Es ist ein Schreibstil, den man mag oder nicht mag. Ich mag es nicht zu sehr, weil ich weiterhin Elemente von Strukturen für Daten verwende und nicht, um Aufrufe von Mitgliedsfunktionen zu emulieren, um in populären objektorientierten Sprachen auszusehen.

+0

Hoffentlich ist Ihr Compiler schlau genug, um das Kopieren der gesamten Struktur in der return-Anweisung zu vermeiden. – joeforker

+2

Wie Joeforker indirekt hervorhebt, ist es besser, einen Zeiger als eine allgemeine Rückkehranweisung zu verwenden. Was ist, wenn Ihre Struktur besonders groß ist? Die meisten Compiler kopieren den Rückgabewert. – Randolpho

+0

Wenn es sehr groß ist, würde ich es einen Zeiger nehmen lassen. aber in diesem Fall (und möglicherweise in fast allen anderen Fällen) ist es ziemlich klein. Ich würde eine große Menge an Daten auf den Heap legen und trotzdem einen Zeiger in die Struktur einfügen. –

0

Nein, Strukturen sind nichts anderes als ein Bündel von Daten. Sie können keine Funktionen innerhalb der Strukturen deklarieren, daher gibt es keine Möglichkeit, einen Konstruktor dafür zu erstellen.

+0

Sie können Funktionen in Strukturen in C oder zumindest Funktionszeiger einfügen. Sie können Konstruktoren in sie einfügen und sie per Konvention aufrufen. Das letzte Mal, als ich sah, dass jemand das machte, war es ein echtes Durcheinander, und es sah für mich nicht wert aus, aber du kannst es schaffen. –

+0

-2 scheint hart für etwas, das vollkommen korrekt ist. +1 für Gerechtigkeit. Sie könnten einen Funktionszeiger namens "constructor" einfügen, aber Sie müssten ihm immer noch mitteilen, dass er auf eine globale Funktion verweisen soll, bevor Sie ihn aufgerufen haben, also können Sie die globale Funktion auch direkt aufrufen! Außerdem würden Sie "dieses" nicht bekommen. – MrZebra

+0

+1 von mir auch .. während es möglich ist, mit Funktionszeigern kreativ zu werden, gibt es sehr wenig Grund, dies zu tun. –

1

Nicht wirklich. Wenn ich mich richtig erinnere, sind Strukturen die am nächsten bei Klassen. Sie reservieren Speicher und füllen die Felder auf. Ich sehe nicht, wie man dafür eine generische Konstruktortyp-Sache machen würde. Theoretisch könnten Sie ein Makro schreiben, das etwas davon tun würde. Bin mir nicht sicher, ob es sich wirklich lohnt.

+0

Können Sie Ihre Makro-Idee entwickeln? – claf

+0

jemals von Werksfunktionen gehört? eine einfache Funktion, die die Struktur zuordnet und auffüllt. keine Notwendigkeit für Makros. Genau das tun C++ - Konstruktoren. – Javier

+0

@javier: Ja, aber nur in einem Java-Kontext. Ich habe C seit 1994 nicht benutzt und habe keine Lust zurück zu gehen :) – paul

2

Unter der Annahme, dass Sie dies in C tun wollen, damit Ihre Frage nicht über structs in C++ ist:

Was ich in der Regel tun, ist, dass ich erstellen Sie einfach eine Funktion, init_whatever, die einen Zeiger auf die Struktur nimmt (oder eine andere Variable) und setzt sie auf die gewünschten Werte.

Wenn es eine globale Variable ist (der auf Null initialisiert wird) Sie könnte Art ein Argument losen Konstruktor simulieren, indem sie ein „initialisiert“ Flag darin hat, und dann lassen Sie andere Funktionen, die Flagge zu überprüfen, und initialisiere die Struktur, wenn das Flag Null ist. Ich bin mir jedoch nicht sicher, ob das eine gute Idee ist.

Und, wie jemand anderes bemerkt, können Sie auch etwas Schreckliches tun könnte mit Makros ...

3

Grundsätzlich C++ erstellt Listen von Zeigern, die die Adressen von Methoden enthalten. Diese Liste wird als Klassendefinition bezeichnet (es gibt einige weitere Daten in der Klasse def, aber wir ignorieren dies für jetzt).

Ein allgemeines Muster, um "Klassen" in reinem C zu haben, ist eine "struct class" zu definieren. Eines der Felder der Struktur ist eine Factory-Funktion, die "Instanzen" der Klasse zurückgibt. Ich schlage vor, ein Makro zu verwenden, um die Würfe zu verstecken:

typedef struct __class * class; 
typedef void (*ctor_ptr)(class); 
struct class { 
    char * name; 
    ctor_ptr ctor; 
    ... destructor and other stuff ... 
} 

#define NEW(clz) ((struct something *)(((struct class *)clz)->ctor(clz))) 

Nun können Sie die Klassen definieren Sie durch die Schaffung von Strukturen vom Typ „struct Klasse“ haben für jede Klasse, die Sie haben und dann den Konstruktor in ihnen gespeicherten Nummern anrufen. Das gleiche gilt für den destructor usw.

Wenn Sie Methoden für Ihre Instanzen wollen, müssen sie in die Klassenstrukturen setzen und einen Zeiger auf die Klasse in der Instanz struct halten:

#define NEW_SOMETHING() ((struct something *)NEW(&something_definition)) 
#define METHOD(inst, arg) ((struct something_class *)(((struct something *)inst)->clz)->method(inst, arg)) 

NEW_SOMETHING schaffen wird eine neue Instanz von "etwas", die die in der Struktur something_definition gespeicherte Klassendefinition verwendet. METHOD ruft eine "Methode" für diese Instanz auf. Beachten Sie, dass Sie für echten Code überprüfen möchten, dass inst tatsächlich eine Instanz von something ist (vergleichen Sie die Klassenzeiger, etwas ähnliches).

Vererbung ist ein bisschen schwierig und als Übung für den Leser übrig.

Beachten Sie, dass Sie alles tun können, was Sie in C++ in reinem C tun können. Ein C++ - Compiler ersetzt Ihre CPU nicht durch etwas anderes. Es braucht nur viel mehr Code, um es zu tun.

Wenn Sie sich ein Beispiel ansehen möchten, lesen Sie glib (Teil des Gtk + -Projekts).

+0

Schon mal von GOBJECT gehört ? – joeforker

+0

@joe: Hast du jemals vom Amiga gehört? :) –

+0

mein Fehler, meine Frage war über Standardkonstruktor, ich möchte einige Feld einer Struktur bei der Deklaration initialisiert haben, ohne einen Konstruktor aufrufen zu müssen. – claf

16

C++ unterscheidet sich von C in diesem Fall in der Hinsicht, dass es keine "Klassen" hat. C (wie viele andere Sprachen) kann jedoch weiterhin für die objektorientierte Programmierung verwendet werden. In diesem Fall kann Ihr Konstruktor eine Funktion sein, die eine Struktur initialisiert. Dies ist das Gleiche wie Konstruktoren (nur eine andere Syntax). Ein weiterer Unterschied ist, dass Sie das Objekt mit malloc() (oder einer Variante) zuordnen müssen. In C++ würden Sie einfach den "neuen" Operator verwenden.

z.B.C++ Code:

class A { 
    public: 
    A() { a = 0; } 
    int a; 
}; 

int main() 
{ 
    A b; 
    A *c = new A; 
    return 0; 
} 

Äquivalent C-Code:

struct A { 
    int a; 
}; 

void init_A_types(struct A* t) 
{ 
    t->a = 0; 
} 

int main() 
{ 
    struct A b; 
    struct A *c = malloc(sizeof(struct A)); 
    init_A_types(&b); 
    init_A_types(c); 
    return 0; 
} 

die Funktion 'init_A_types' fungiert als Konstruktor würde in C++.

1

Sie können sich einen C++ - Compiler ansehen. Es bietet Ihnen mehr objektorientierte Funktionen, als Sie sich in einer Sprache mit einer C-ähnlichen Syntax eleganter und standardmäßiger erinnern können, als zu versuchen, Konstruktoren usw. mit Bibliotheken und Makros auf C zu schrauben.

Wenn das für Sie nicht funktioniert, werfen Sie einen Blick auf GObject. http://en.wikipedia.org/wiki/GObject. Es ist eine Aufgabe System für C verwendet in GTK und Gnome, die so ziemlich Kurbeln "Objekte in C" bis 11.

Aus Wikipedia:

The GLib Object System oder GObject, eine kostenlose Software-Bibliothek (überdacht durch die LGPL), die ein tragbares Objektsystem und transparente sprachübergreifende Interoperabilität bietet.

Das System unterstützt Konstruktoren, Destruktoren, einzelne Vererbung, Schnittstellen, virtuelle öffentliche und private Methoden und so weiter. Es ist auch viel mühsamer und schwieriger als die gleichen Dinge in C++ zu tun. Habe Spaß!

10

Lassen Sie uns über die vollständige technische Lösung sprechen, die in den alten Tagen als Best Practice galt.

Das Problem mit Strukturen ist, dass alles öffentlich ist, so dass keine Daten versteckt sind.

Wir können das beheben.

Sie erstellen zwei Headerdateien. Einer ist die "öffentliche" Header-Datei, die von Clients Ihres Codes verwendet wird. Es enthält Definitionen wie folgt aus:

typedef struct t_ProcessStruct *t_ProcessHandle; 

extern t_ProcessHandle NewProcess(); 
extern void DisposeProcess(t_ProcessHandle handle); 

typedef struct t_PermissionsStruct *t_PermissionsHandle; 

extern t_PermissionsHandle NewPermissions(); 
extern void DisposePermissions(t_PermissionsHandle handle); 

extern void SetProcessPermissions(t_ProcessHandle proc, t_PermissionsHandle perm); 

dann Sie eine private Header-Datei erstellen, die Definitionen wie diese enthält:

typedef void (*fDisposeFunction)(void *memoryBlock); 

typedef struct { 
    fDisposeFunction _dispose; 
} t_DisposableStruct; 

typedef struct { 
    t_DisposableStruct_disposer; /* must be first */ 
    PID _pid; 
    /* etc */ 
} t_ProcessStruct; 

typedef struct { 
    t_DisposableStruct_disposer; /* must be first */ 
    PERM_FLAGS _flags; 
    /* etc */ 
} t_PermissionsStruct; 

und dann in Ihrer Implementierung können Sie etwas tun:

static void DisposeMallocBlock(void *process) { if (process) free(process); } 

static void *NewMallocedDisposer(size_t size) 
{ 
    assert(size > sizeof(t_DisposableStruct); 
    t_DisposableStruct *disp = (t_DisposableStruct *)malloc(size); 
    if (disp) { 
     disp->_dispose = DisposeMallocBlock; 
    } 
    return disp; 
} 

static void DisposeUsingDisposer(t_DisposableStruct *ds) 
{ 
    assert(ds); 
    ds->_dispose(ds); 
} 

t_ProcessHandle NewProcess() 
{ 
    t_ProcessHandle proc = (t_ProcessHandle)NewMallocedDisposer(sizeof(t_ProcessStruct)); 
    if (proc) { 
     proc->PID = NextPID(); /* etc */ 
    } 
    return proc; 
} 

void DisposeProcess(t_ProcessHandle proc) 
{ 
    DisposeUsingDisposer(&(proc->_disposer)); 
} 

Was passiert ist, dass Sie Forward-Deklarationen für Ihre Strukturen in Ihren öffentlichen Header-Dateien machen. Jetzt sind Ihre Strukturen undurchsichtig, was bedeutet, dass Kunden nicht mit ihnen schwänzen können. Dann fügen Sie in der vollständigen Deklaration einen Destruktor am Anfang jeder Struktur ein, die Sie generisch aufrufen können. Sie können den gleichen Malloc-Allokator für alle die gleiche Dispose-Funktion verwenden und so. Sie machen öffentliche Set/Get-Funktionen für die Elemente, die Sie anzeigen möchten.

Plötzlich ist Ihr Code viel vernünftiger. Sie können nur Strukturen von Zuweisern erhalten oder diese Anrufverteiler bedienen, was bedeutet, dass Sie die Initialisierung des Engpasses blockieren können. Sie bauen Destruktoren ein, damit das Objekt zerstört werden kann. Und du gehst. Übrigens, ein besserer Name als t_DisposableStruct könnte t_vTableStruct sein, denn genau das ist es. Sie können jetzt virtuelle Vererbung erstellen, indem Sie eine vTableStruct verwenden, bei der es sich um Funktionszeiger handelt. Sie können auch Dinge tun, die Sie in einer reinen oo-Sprache nicht tun können (in der Regel), indem Sie ausgewählte Elemente der vtable im laufenden Betrieb ändern.

Der wichtige Punkt ist, dass ist ein Engineering-Muster für die Herstellung von Strukturen sicher und initialisierbar.

+0

Es ist ironisch, wie einer der "Vorteile" von C++ ist, Datenkapselung durch geschützte/private Spezifizierer bereitzustellen, und doch beinhalten die meisten seiner Verwendungsmuster Klassen, die allen anderen ihren Mut zeigen (aber nicht erlauben) jemand, um sie zu berühren, natürlich). Man könnte den gleichen Ansatz wie in C nehmen, aber das wird mehr als nicht entmutigt, weil es "nicht OOP" ist, oder den pimpl-Ansatz nehmen, der meiner Erfahrung nach nur Overhead- und Boilerplate-Code einführt. – Tomis

2

Hier ist eine kleine Makro Magie um malloc(), memcpy() und C99 Verbindung Literale:

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

#define new(TYPE, ...) memdup(&(TYPE){ __VA_ARGS__ }, sizeof(TYPE)) 

void * memdup(const void * obj, size_t size) 
{ 
    void * copy = malloc(size); 
    return copy ? memcpy(copy, obj, size) : NULL; 
} 

struct point 
{ 
    int x; 
    int y; 
}; 

int main() 
{ 
    int * i = new(int, 1); 
    struct point * p = new(struct point, 2, 3); 

    printf("%i %i %i", *i, p->x, p->y); 

    return 0; 
} 
2

Funktionen Verwendung von Strukturen zu schaffen und zu entsorgen ist bereits erwähnt worden. Aber in einem Kommentar erwähnten Sie, dass Sie einen "Standardkonstruktor" wollten - ich denke, Sie meinen, dass Sie einige (alle?) Der Strukturfelder auf Standardwerte initialisieren möchten.

Dies geschieht in C mit einigen Codierungskonventionen - entweder Funktionen, Makros oder eine Mischung. Ich mache es in der Regel so etwas wie die folgenden -

struct some_struct { 
    int a; 
    float b; 
}; 
#define some_struct_DEFAULT { 0, 0.0f} 
struct some_struct *some_struct_create(void) { 
    struct some_struct *ptr = malloc(sizeof some_struct); 
    if(!ptr) 
     return ptr; 

    *ptr = some_struct_DEFAULT; 
    return ptr; 
} 
// (...) 
struct some_struct on_stack = some_struct_DEFAULT; 
struct some_struct *on_heap = some_struct_create(); 
1

Stilllegung ist das einfache Programm, das die Verwendung eines Konstruktor macht. Die Funktion "default_constructor" wird innerhalb von main ohne expliziten Funktionsaufruf aufgerufen.

#include  <stdio.h> 

void __attribute__ ((constructor)) default_constructor() 
{ 
    printf("%s\n", __FUNCTION__); 
} 

int main() 
{ 
    printf("%s\n",__FUNCTION__); 
    return 0; 
} 

Ausgang: default_constructor Haupt

Innerhalb der Konstruktorfunktion Sie einige Initialisierungsanweisungen im Konstruktor Funktion enthalten können und schließlich in dem destructor befreien können tun, als Brache,

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

struct somestruct 
{ 
    int  empid; 
    char * name; 
}; 

struct somestruct * str1; 

void __attribute__ ((constructor)) a_constructor() 
{ 
    str1 = (struct somestruct *) malloc (sizeof(struct somestruct)); 
    str1 -> empid = 30228; 
    str1 -> name = "Nandan"; 
} 

void __attribute__ ((destructor)) a_destructor() 
{ 
    free(str1); 
} 

int main() 
{ 
    printf("ID = %d\nName = %s\n", str1 -> empid, str1 -> name); 
    return 0; 
} 

Hoffnung das wird dir helfen.