Wie erstelle ich ein Singleton in C?

Lesezeit: 8 Minuten

Benutzer-Avatar
Liran Orevi

Was ist der beste Weg, um ein Singleton in C zu erstellen? Eine parallele Lösung wäre schön.

Mir ist bewusst, dass C nicht die erste Sprache ist, die Sie für einen Singleton verwenden würden.

Benutzer-Avatar
dirkly

Erstens ist C nicht für die OO-Programmierung geeignet. Du würdest den ganzen Weg kämpfen, wenn du das tust. Zweitens sind Singletons nur statische Variablen mit einer gewissen Kapselung. Sie können also eine statische globale Variable verwenden. Globale Variablen sind jedoch typischerweise mit viel zu vielen Übeln verbunden. Sie könnten ansonsten eine lokale statische Funktionsvariable wie folgt verwenden:

 int *SingletonInt() {
     static int instance = 42;
     return &instance;
 }

oder ein intelligenteres Makro:

#define SINGLETON(t, inst, init) t* Singleton_##t() { \
                 static t inst = init;               \
                 return &inst;                       \
                }

#include <stdio.h>  

/* actual definition */
SINGLETON(float, finst, 4.2);

int main() {
    printf("%f\n", *(Singleton_float()));
    return 0;
}

Und denken Sie schließlich daran, dass Singletons meistens missbraucht werden. Es ist schwierig, sie richtig hinzubekommen, besonders in Umgebungen mit mehreren Threads …

  • Danke, nur überprüft – Diese Lösung ist nicht an Multithread-Umgebungen angepasst, richtig?

    – Liran Orevi

    29. April 2009 um 19:00 Uhr

  • Diese Art der Initialisierung ist in einer Multithread-Umgebung sicher, wenn keine andere Abhängigkeit der Singleton-Instanz von anderen statischen Elementen im Programm vorhanden ist Dies aus ihrer C++-Implementierung.) Der Nachteil ist, dass Sie nur einen konstanten Ausdruck verwenden können. Ein Double-Checked-Locking-basiertes Singleton für eine Multithread-Umgebung benötigt eine Thread-Bibliothek für Mutexe usw.

    – dirkgent

    29. April 2009 um 19:14 Uhr

  • C ist mehr als geeignet für OOP. Tatsächlich sind die einzigen Funktionen von C++, die OOP wirklich helfen, syntaktischer Zucker.

    – Javier

    29. April 2009 um 19:19 Uhr

  • Beginnen wir mit Polymorphismus in C?

    – dirkgent

    29. April 2009 um 19:23 Uhr

  • Ich bestreite nicht, dass dies möglich ist, aber nicht ohne übermäßig viel Zeit und Mühe. Beispiel: Polymorphe Funktionen mit Makros zu hacken ist eine Möglichkeit – aber Sie verlieren die Typsicherheit. Es gibt auch kein RTTI in C. (Und ja, C hat einen begrenzten Polymorphismus, denken Sie an die Operatoren +, – usw.)

    – dirkgent

    29. April 2009 um 19:26 Uhr

Das müssen Sie nicht. C verfügt bereits über globale Variablen, sodass Sie keine Problemumgehung benötigen, um sie zu simulieren.

  • Ich verstehe, wie ein Global verwendet werden kann, aber die Tatsache, dass C++ auch globale Variablen hat und dass in C++ die Singleton-Implementierung normalerweise nicht global ist, lässt mich nach mehr streben.

    – Liran Orevi

    30. April 2009 um 17:57 Uhr

  • C++ ist eine objektorientierte Sprache; C ist nicht. Sicher, du kann mache OOP in C, aber es ist hässlich, hacky und mühsam, damit umzugehen. Ebenso du kann Schreiben Sie fast-C in C++, aber warum verwenden Sie dann C++? Verwenden Sie also wirklich nur eine globale. Es macht keinen Sinn zu versuchen zu verschleiern, was Sie tun, indem Sie es mit einer Reihe von funktionslokalen statischen Variablen und Präprozessor-Makros verschleiern.

    – Adam Jaskiewicz

    30. April 2009 um 18:34 Uhr

  • Kann nicht mehr zustimmen, Singletons werden oft von Programmierern verwendet, die sich irgendwie schämen, Globals zu verwenden.

    – Laurent

    1. Juni 2012 um 8:44 Uhr

  • Imho ist die Verwendung globaler Variablen obskurer als eine DB db = get_db_instance ()

    – gpilotino

    2. Juli 2012 um 19:47 Uhr

  • Beantwortet nicht die Frage von OP. C++ hat auch globale Variablen; würden Sie die gleiche Logik anwenden und globale Variablen über ein Singleton in C++ empfehlen? Bitte lesen Sie die ausführliche Dokumentation für die Gründe, einen Singleton über Instanzvariablen zu verwenden. Bedenken Sie, dass C++ Compiler wurden zuerst in ‘C’ geschrieben.

    – Technikbegeistert

    15. Februar 2018 um 19:33 Uhr


Benutzer-Avatar
justinhj

Es ist so ziemlich dasselbe wie die C++-Version. Haben Sie einfach eine Funktion, die einen Instanzzeiger zurückgibt. Es kann eine statische Variable innerhalb der Funktion sein. Umschließen Sie den Funktionsrumpf je nach Plattform mit einem kritischen Abschnitt oder einem pthread-Mutex.

#include <stdlib.h>

struct A
{
    int a;
    int b;
};

struct A* getObject()
{
    static struct A *instance = NULL;

    // do lock here
    if(instance == NULL)
    {
        instance = malloc(sizeof(*instance));
        instance->a = 1;
        instance->b = 2;
    }
    // do unlock

    return instance;
};

Beachten Sie, dass Sie auch eine Funktion benötigen, um den Singleton freizugeben. Vor allem, wenn es Systemressourcen greift, die beim Beenden des Prozesses nicht automatisch freigegeben werden.

BEARBEITEN: Meine Antwort geht davon aus, dass das Singleton, das Sie erstellen, etwas komplex ist und einen mehrstufigen Erstellungsprozess hat. Wenn es sich nur um statische Daten handelt, wählen Sie eine globale, wie andere vorgeschlagen haben.

Ein Singleton in C wird sehr seltsam sein. . . Ich habe noch nie ein Beispiel für “objektorientiertes C” gesehen, das besonders elegant aussah. Ziehen Sie nach Möglichkeit die Verwendung von C++ in Betracht. Mit C++ können Sie auswählen, welche Funktionen Sie verwenden möchten, und viele Leute verwenden es einfach als “besseres C”.

Unten sehen Sie ein ziemlich typisches Muster für eine einmalige Initialisierung ohne Sperre. Der InterlockCompareExchangePtr tauscht den neuen Wert atomar aus, wenn der vorherige null ist. Dies schützt, wenn mehrere Threads gleichzeitig versuchen, den Singleton zu erstellen, nur einer gewinnt. Die anderen löschen ihr neu erstelltes Objekt.

MyObj* g_singleton; // MyObj is some struct.

MyObj* GetMyObj()
{
    MyObj* singleton;
    if (g_singleton == NULL)
    {
        singleton = CreateNewObj();

        // Only swap if the existing value is null.  If not on Windows,
        // use whatever compare and swap your platform provides.
        if (InterlockCompareExchangePtr(&g_singleton, singleton, NULL) != NULL)
        {
              DeleteObj(singleton);
        }
    }

    return g_singleton;
}

DoSomethingWithSingleton(GetMyObj());

Hier ist eine andere Perspektive: Jede Datei in einem C-Programm ist effektiv eine Singleton-Klasse, die zur Laufzeit automatisch instanziiert wird und nicht abgeleitet werden kann.

  • Globale statische Variablen sind Ihre privaten Klassenmitglieder.
  • Globale nicht statische sind öffentlich (deklarieren Sie sie einfach mit extern in irgendeiner Header-Datei).
  • Statische Funktionen sind private Methoden
  • Nicht statische Funktionen sind die öffentlichen.

Geben Sie allem ein richtiges Präfix und jetzt können Sie verwenden my_singleton_method() anstelle my_singleton.method().

Wenn Ihr Singleton komplex ist, können Sie a schreiben generate_singleton() -Methode, um sie vor der Verwendung zu initialisieren, aber dann müssen Sie sicherstellen, dass alle anderen öffentlichen Methoden prüfen, ob sie aufgerufen wurde, und einen Fehler ausgeben, wenn dies nicht der Fall ist.

  • Es scheint sicherer zu sein, Accessor-Funktionen zu verwenden als globale nicht statische Variablen.

    – Technikbegeistert

    15. Februar 2018 um 19:41 Uhr

  • @Technophile Es ist nicht sicherer, es ist flexibler. Wenn Sie Zugriff auf eine Variable gewähren, geben Sie Zugriff auf eine Variable, egal ob es sich um einen direkten Zugriff oder über die Accessor-Funktion handelt. Der Vorteil der Verwendung von Zugriffsfunktionen besteht darin, dass Sie über die einfache E/A hinaus zusätzliche Richtlinien implementieren können.

    Benutzer4209701

    18. Februar 2018 um 12:10 Uhr

  • „Sicherer“ in Bezug auf den Zugriff zumindest über eine API, die mit Gültigkeitsprüfungen usw. erweitert werden kann. Meinen Sie das mit „zusätzlicher Richtlinie“?

    – Technikbegeistert

    26. Februar 2018 um 13:35 Uhr

  • Ja das meinte ich

    Benutzer4209701

    1. April 2018 um 14:21 Uhr

Ich denke, diese Lösung könnte die einfachste und beste für die meisten Anwendungsfälle sein …

In diesem Beispiel erstelle ich eine globale Dispatch-Warteschlange mit einer einzelnen Instanz, was Sie beispielsweise auf jeden Fall tun würden, wenn Sie Dispatch-Quellereignisse von mehreren Objekten verfolgen würden. In diesem Fall könnte jedes Objekt, das die Warteschlange auf Ereignisse überwacht, benachrichtigt werden, wenn eine neue Aufgabe zur Warteschlange hinzugefügt wird. Sobald die globale Warteschlange eingerichtet ist (via queue_ref()), es kann mit dem verwiesen werden queue Variable in jeder Datei, in der die Header-Datei enthalten ist (Beispiele sind unten angegeben).

In einer meiner Implementierungen habe ich angerufen queue_ref() in AppDelegate.m (main.c würde auch funktionieren). Dieser Weg, queue wird initialisiert, bevor irgendein anderes aufrufendes Objekt versucht, darauf zuzugreifen. Bei den restlichen Objekten habe ich einfach angerufen queue. Das Zurückgeben eines Werts aus einer Variablen ist viel schneller als das Aufrufen einer Funktion und das anschließende Überprüfen des Werts der Variablen vor der Rückgabe.

In GlobalQueue.h:

#ifndef GlobalQueue_h
#define GlobalQueue_h

#include <stdio.h>
#include <dispatch/dispatch.h>

extern dispatch_queue_t queue;

extern dispatch_queue_t queue_ref(void);

#endif /* GlobalQueue_h */

In GlobalQueue.c:

#include "GlobalQueue.h"

dispatch_queue_t queue;

dispatch_queue_t queue_ref(void) {
    if (!queue) {
        queue = dispatch_queue_create_with_target("GlobalDispatchQueue", DISPATCH_QUEUE_SERIAL, dispatch_get_main_queue());
    }
    
    return queue;
}

Benutzen:

  1. #include "GlobalQueue.h" in jeder Objective-C- oder C-Implementierungsquelldatei.
  2. Anruf queue_ref() um die Dispatch-Warteschlange zu verwenden. Einmal queue_ref() aufgerufen wurde, kann die Warteschlange über die genutzt werden queue Variable in allen Quelldateien

Beispiele:

Aufruf von queue_ref():

dispatch_queue_t serial_queue_with_queue_target = dispatch_queue_create_with_target("serial_queue_with_queue_target", DISPATCH_QUEUE_SERIAL, **queue_ref()**);

Anrufwarteschlange:

dispatch_queue_t serial_queue_with_queue_target = dispatch_queue_create_with_target("serial_queue_with_queue_target", DISPATCH_QUEUE_SERIAL, **queue**));]  

  • Es scheint sicherer zu sein, Accessor-Funktionen zu verwenden als globale nicht statische Variablen.

    – Technikbegeistert

    15. Februar 2018 um 19:41 Uhr

  • @Technophile Es ist nicht sicherer, es ist flexibler. Wenn Sie Zugriff auf eine Variable gewähren, geben Sie Zugriff auf eine Variable, egal ob es sich um einen direkten Zugriff oder über die Accessor-Funktion handelt. Der Vorteil der Verwendung von Zugriffsfunktionen besteht darin, dass Sie über die einfache E/A hinaus zusätzliche Richtlinien implementieren können.

    Benutzer4209701

    18. Februar 2018 um 12:10 Uhr

  • „Sicherer“ in Bezug auf den Zugriff zumindest über eine API, die mit Gültigkeitsprüfungen usw. erweitert werden kann. Meinen Sie das mit „zusätzlicher Richtlinie“?

    – Technikbegeistert

    26. Februar 2018 um 13:35 Uhr

  • Ja das meinte ich

    Benutzer4209701

    1. April 2018 um 14:21 Uhr

Benutzer-Avatar
Nr

Mach einfach

void * getSingleTon() {
    static Class object = (Class *)malloc( sizeof( Class ) );
    return &object;
}

was auch in einer gleichzeitigen Umgebung funktioniert.

  • Es sollte sein static Class * object = ...; return object;.

    – alk

    7. September 2015 um 10:33 Uhr

1365960cookie-checkWie erstelle ich ein Singleton in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy