¿Cómo crear un Singleton en C?

9 minutos de lectura

¿Como crear un Singleton en C
Liran Orevi

¿Cuál es la mejor manera de crear un singleton en C? Una solución concurrente estaría bien.

Soy consciente de que C no es el primer idioma que usaría para un singleton.

1647539591 499 ¿Como crear un Singleton en C
dirkgently

Primero, C no es adecuado para la programación orientada a objetos. Estarías peleando todo el camino si lo haces. En segundo lugar, los singletons son solo variables estáticas con alguna encapsulación. Entonces puedes usar una variable global estática. Sin embargo, las variables globales suelen tener demasiados males asociados con ellas. De lo contrario, podría usar una variable estática local de función, como esta:

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

o una macro más inteligente:

#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;
}

Y, por último, recuerda que los singletons son los que más se abusan. Es difícil hacerlo bien, especialmente en entornos de subprocesos múltiples…

  • Gracias, solo comprobando: esta solución no está adaptada a entornos de subprocesos múltiples, ¿verdad?

    – Liran Orevi

    29 de abril de 2009 a las 19:00

  • Esta forma de inicializar es segura en un entorno de subprocesos múltiples cuando no hay otra dependencia de la instancia singleton en cualquier otro estático presente dentro del programa. esto fuera de su implementación de C ++). La desventaja es que solo puede usar una expresión constante. Un singleton basado en bloqueo verificado dos veces para un entorno de subprocesos múltiples necesitará una biblioteca de subprocesos para mutexes, etc.

    – dirkgently

    29 de abril de 2009 a las 19:14

  • C es más que adecuado para programación orientada a objetos. de hecho, las únicas características de C++ que realmente ayudan a OOP son el azúcar sintáctico.

    – Javier

    29 de abril de 2009 a las 19:19

  • Comencemos con el polimorfismo en C?

    – dirkgently

    29 de abril de 2009 a las 19:23

  • No niego que esto se puede hacer, pero no sin gastar una cantidad excesiva de tiempo y esfuerzo. Por ejemplo: piratear funciones polimórficas usando macros es una forma de hacerlo, pero pierde la seguridad de tipo. Tampoco hay RTTI en C. (Y sí, C tiene un polimorfismo limitado, piense en los operadores +, -, etc.)

    – dirkgently

    29 de abril de 2009 a las 19:26

No es necesario. C ya tiene variables globales, por lo que no necesita una solución alternativa para simularlas.

  • Entiendo la forma en que se puede usar un global, pero el hecho de que C ++ también tenga variables globales, y que en C ++ la implementación de singleton generalmente no sea con global, me hace aspirar a más.

    – Liran Orevi

    30 de abril de 2009 a las 17:57

  • C++ es un lenguaje orientado a objetos; C no lo es. Seguro lata hacer programación orientada a objetos en C, pero es feo, hacky y un dolor de tratar. Del mismo modo, usted lata escriba casi-C en C++, pero entonces, ¿por qué está usando C++? Entonces, realmente, solo usa un global. No tiene sentido tratar de disfrazar lo que está haciendo ofuscándolo con un montón de variables estáticas locales de función y macros de preprocesador.

    – Adam Jaskiewicz

    30 de abril de 2009 a las 18:34

  • No podría estar más de acuerdo, los programadores a menudo usan singletons que de alguna manera se avergüenzan de usar globales.

    – laurent

    1 de junio de 2012 a las 8:44

  • en mi humilde opinión, el uso de variables globales es más oscuro que tener un DB db = get_db_instance ()

    – gpilotino

    2 de julio de 2012 a las 19:47

  • No responde la pregunta de OP. C++ también tiene variables globales; ¿Aplicaría la misma lógica y recomendaría variables globales sobre un Singleton en C++? Consulte la amplia documentación sobre las razones para usar un Singleton sobre variables de instancia. Considere que C++ compiladores primero se escribieron en ‘C’.

    – Tecnófilo

    15 de febrero de 2018 a las 19:33


¿Como crear un Singleton en C
justinhj

Es más o menos lo mismo que la versión de C++. Solo tenga una función que devuelva un puntero de instancia. Puede ser una variable estática dentro de la función. Envuelva el cuerpo de la función con una sección crítica o pthread mutex, según la plataforma.

#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;
};

Tenga en cuenta que también necesitaría una función para liberar el singleton. Especialmente si toma algún recurso del sistema que no se libera automáticamente al salir del proceso.

EDITAR: Mi respuesta supone que el singleton que está creando es algo complejo y tiene un proceso de creación de varios pasos. Si se trata solo de datos estáticos, vaya con un global como otros han sugerido.

Un singleton en C será muy raro. . . Nunca he visto un ejemplo de “C orientado a objetos” que se vea particularmente elegante. Si es posible, considere usar C++. C ++ le permite elegir qué funciones desea usar, y muchas personas simplemente lo usan como un “mejor C”.

A continuación se muestra un patrón bastante típico para la inicialización única sin bloqueo. InterlockCompareExchangePtr intercambia atómicamente el nuevo valor si el anterior es nulo. Esto protege si varios subprocesos intentan crear el singleton al mismo tiempo, solo uno ganará. Los demás eliminarán su objeto recién creado.

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());

Aquí hay otra perspectiva: cada archivo en un programa C es efectivamente una clase única que se instancia automáticamente en tiempo de ejecución y no se puede subclasificar.

  • Las variables estáticas globales son los miembros de su clase privada.
  • Los no estáticos globales son públicos (simplemente declárelos usando extern en algún archivo de cabecera).
  • Las funciones estáticas son métodos privados.
  • Las funciones no estáticas son las públicas.

Dale a todo un prefijo adecuado y ahora puedes usar my_singleton_method() en lugar de my_singleton.method().

Si su singleton es complejo, puede escribir un generate_singleton() método para inicializarlo antes de usarlo, pero luego debe asegurarse de que todos los demás métodos públicos verifiquen si se llamó y si no se produce un error.

  • Parece más seguro usar funciones de acceso en lugar de variables globales no estáticas.

    – Tecnófilo

    15 de febrero de 2018 a las 19:41

  • @Technophile No es más seguro, es más flexible. Si está dando acceso a una variable, está dando acceso a una variable, no importa si es acceso directo o mediante la función de acceso. El beneficio de usar funciones de acceso es que puede implementar una política adicional más allá de la E/S directa.

    usuario4209701

    18 de febrero de 2018 a las 12:10

  • ‘Más seguro’ en términos de al menos canalizar el acceso a través de una API, que se puede ampliar con comprobaciones de validez, etc. ¿Es esto lo que quiere decir con “política adicional”?

    – Tecnófilo

    26 de febrero de 2018 a las 13:35

  • si eso es lo que quise decir

    usuario4209701

    1 de abril de 2018 a las 14:21

Creo que esta solución podría ser la más simple y la mejor para la mayoría de los casos de uso…

En este ejemplo, estoy creando una cola de envío global de instancia única, lo que definitivamente haría, por ejemplo, si estuviera rastreando eventos de origen de envío de múltiples objetos; en ese caso, cada objeto que escuche la cola de eventos podría recibir una notificación cuando se agregue una nueva tarea a la cola. Una vez que se establece la cola global (a través de queue_ref()), puede ser referenciado con el queue variable en cualquier archivo en el que se incluya el archivo de encabezado (a continuación se proporcionan ejemplos).

En una de mis implementaciones, llamé queue_ref() en AppDelegate.m (main.c también funcionaría). De esa manera, queue se inicializará antes de que cualquier otro objeto de llamada intente acceder a él. En los objetos restantes, simplemente llamé queue. Devolver un valor de una variable es mucho más rápido que llamar a una función y luego verificar el valor de la variable antes de devolverlo.

En 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 */

En 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;
}

Usar:

  1. #include "GlobalQueue.h" en cualquier archivo fuente de implementación Objective-C o C.
  2. Llamada queue_ref() para utilizar la cola de despacho. Una vez queue_ref() ha sido llamado, la cola se puede utilizar a través de la queue variable en todos los archivos fuente

Ejemplos:

Llamando a 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()**);

Cola de llamadas:

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

  • Parece más seguro usar funciones de acceso en lugar de variables globales no estáticas.

    – Tecnófilo

    15 de febrero de 2018 a las 19:41

  • @Technophile No es más seguro, es más flexible. Si está dando acceso a una variable, está dando acceso a una variable, no importa si es acceso directo o mediante la función de acceso. El beneficio de usar funciones de acceso es que puede implementar una política adicional más allá de la E/S directa.

    usuario4209701

    18 de febrero de 2018 a las 12:10

  • ‘Más seguro’ en términos de al menos canalizar el acceso a través de una API, que se puede ampliar con comprobaciones de validez, etc. ¿Es esto lo que quiere decir con “política adicional”?

    – Tecnófilo

    26 de febrero de 2018 a las 13:35

  • si eso es lo que quise decir

    usuario4209701

    1 de abril de 2018 a las 14:21

1647539592 397 ¿Como crear un Singleton en C
hermano

Solo haz

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

que también funciona en un entorno concurrente.

  • Debería ser static Class * object = ...; return object;.

    – alk

    7 sep 2015 a las 10:33

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad