Как создать синглтон в C?

Как лучше всего создать синглтон в C? Параллельное решение было бы неплохо.

Я знаю, что C — не первый язык, который вы бы использовали для синглтона.


person Liran Orevi    schedule 29.04.2009    source источник


Ответы (7)


Во-первых, C не подходит для объектно-ориентированного программирования. Если ты это сделаешь, ты будешь драться всю дорогу. Во-вторых, синглтоны — это просто статические переменные с некоторой инкапсуляцией. Таким образом, вы можете использовать статическую глобальную переменную. Однако с глобальными переменными обычно связано слишком много проблем. В противном случае вы могли бы использовать локальную статическую переменную функции, например:

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

или более умный макрос:

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

И, наконец, помните, что синглтонами чаще всего злоупотребляют. Их трудно правильно понять, особенно в многопоточных средах...

person dirkgently    schedule 29.04.2009
comment
Спасибо, просто проверяю. Это решение не адаптировано для многопоточных сред, верно? - person Liran Orevi; 29.04.2009
comment
Этот способ инициализации безопасен в многопоточной среде, когда нет другой зависимости экземпляра singleton от любого другого статического объекта, присутствующего в программе. (IIRC, существует некоторая неоднозначность относительно порядка инициализации статики, поэтому Гамма и др. оставили это из их реализации на С++.) Недостатком является то, что вы можете использовать только константное выражение. Синглтон на основе блокировки с двойной проверкой для многопоточной среды потребует библиотеки потоков для мьютексов и т. д. - person dirkgently; 29.04.2009
comment
C более чем подходит для ООП. на самом деле, единственные возможности C++, которые действительно помогают ООП, — это синтаксический сахар. - person Javier; 29.04.2009
comment
Начнем с полиморфизма в C? - person dirkgently; 29.04.2009
comment
Я не отрицаю, что это можно сделать, но не без чрезмерных затрат времени и усилий. Например: взломать полиморфные функции с помощью макросов — это один из способов, но вы потеряете безопасность типов. В C также нет RTTI. (И да, C имеет ограниченный полиморфизм, подумайте об операторах +, - и т. д.) - person dirkgently; 29.04.2009
comment
Поскольку я видел этот код, я пытаюсь сделать это с помощью структуры. У вас есть пример кода, чтобы показать мне, как это сделать? - person Leandro Lima; 12.04.2013
comment
Можете объяснить, почему макрос умнее? И что вы думаете об этом решении? stackoverflow.com/a/5171844/1763602 - person Marco Sulla; 26.08.2016

Вам не нужно. C уже имеет глобальные переменные, поэтому вам не нужен обходной путь для их имитации.

person Adam Jaskiewicz    schedule 29.04.2009
comment
Я понимаю, как можно использовать глобальные переменные, но тот факт, что в C++ также есть глобальные переменные, и что в C++ одноэлементная реализация обычно не использует глобальные переменные, заставляет меня стремиться к большему. - person Liran Orevi; 30.04.2009
comment
C++ — это объектно-ориентированный язык; С нет. Конечно, вы можете реализовать ООП на C, но это уродливо, хакерски и с этим сложно иметь дело. Точно так же вы можете писать почти на C на C++, но тогда почему вы используете C++? Так что действительно, просто используйте глобальный. Нет смысла пытаться замаскировать то, что вы делаете, запутывая его кучей локальных статических переменных и макросов препроцессора. - person Adam Jaskiewicz; 30.04.2009
comment
Не могу не согласиться, синглтоны часто используются программистами, которым как-то стыдно использовать глобалы. - person laurent; 01.06.2012
comment
использование глобальных переменных imho более неясно, чем наличие БД db = get_db_instance() - person gpilotino; 02.07.2012
comment
Не отвечает на вопрос ОП. C++ также имеет глобальные переменные; Вы бы применили ту же логику и порекомендовали бы глобальные переменные вместо Singleton в C++? Пожалуйста, обратитесь к обширной документации, чтобы узнать о причинах использования Singleton над переменными экземпляра. Учтите, что компиляторы С++ сначала были написаны на «С». - person Technophile; 15.02.2018

Это почти то же самое, что и версия C++. Просто есть функция, которая возвращает указатель экземпляра. Это может быть статическая переменная внутри функции. Оберните тело функции критической секцией или мьютексом pthread, в зависимости от платформы.

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

Обратите внимание, что вам также понадобится функция для освобождения синглтона. Особенно, если он захватывает какие-либо системные ресурсы, которые не освобождаются автоматически при выходе из процесса.

person justinhj    schedule 29.04.2009

РЕДАКТИРОВАТЬ: мой ответ предполагает, что синглтон, который вы создаете, несколько сложен и имеет многоэтапный процесс создания. Если это просто статические данные, используйте глобальные данные, как предлагали другие.

Синглтон в C будет очень странным. . . Я никогда не видел пример "объектно-ориентированного C", который выглядел бы особенно элегантно. Если возможно, рассмотрите возможность использования C++. C++ позволяет вам выбирать, какие функции вы хотите использовать, и многие люди просто используют его как «лучший C».

Ниже приведен довольно типичный шаблон одноразовой инициализации без блокировки. InterlockCompareExchangePtr атомарно заменяет новое значение, если предыдущее равно null. Это защищает, если несколько потоков попытаются одновременно создать синглтон, только один выиграет. Остальные удалят свой вновь созданный объект.

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());
person Michael    schedule 29.04.2009
comment
+1 Хороший шаблон - хотя этот метод предполагает, что вам разрешено вызывать CreateNewObj более одного раза. В зависимости от особенностей ресурсов, обрабатываемых синглтоном, у вас может не быть такой роскоши. - person Eclipse; 29.04.2009
comment
Правда, если это невозможно, вам нужно использовать более тяжелое решение, например, замок. - person Michael; 29.04.2009
comment
В системе с pthreads можно использовать pthread_once() для запуска кода инициализации только один раз. - person Blair Zajac; 30.04.2009
comment
Vista также добавила IninitOnceExecuteOnce, но это исключает XP или 2k3. - person Michael; 30.04.2009

Вот еще одна точка зрения: каждый файл в программе на C фактически представляет собой одноэлементный класс, который автоматически создается во время выполнения и не может быть подклассом.

  • Глобальные статические переменные являются вашими приватными членами класса.
  • Глобальные нестатические объекты являются общедоступными (просто объявите их с помощью extern в каком-нибудь заголовочном файле).
  • Статические функции — это приватные методы
  • Нестатические функции являются общедоступными.

Дайте всем правильный префикс, и теперь вы можете использовать my_singleton_method() вместо my_singleton.method().

Если ваш синглтон сложный, вы можете написать метод generate_singleton() для его инициализации перед использованием, но тогда вам нужно убедиться, что все другие общедоступные методы проверяют, был ли он вызван, и выдают ошибку, если нет.

person Community    schedule 29.03.2017
comment
Кажется более безопасным использовать функции доступа, а не глобальные нестатические переменные. - person Technophile; 15.02.2018
comment
@Technophile Это не безопаснее, это более гибко. Если вы предоставляете доступ к переменной, вы предоставляете доступ к переменной, не имеет значения, прямой ли это доступ или через функцию доступа. Преимущество использования функций доступа заключается в том, что вы можете реализовать дополнительную политику помимо простого ввода-вывода. - person ; 18.02.2018
comment
«Безопаснее» с точки зрения, по крайней мере, направления доступа через API, который может быть расширен проверками достоверности и т. д. Это то, что вы подразумеваете под дополнительной политикой? - person Technophile; 26.02.2018
comment
Да, это то, что я имел в виду - person ; 01.04.2018

Я думаю, что это решение может быть самым простым и лучшим для большинства случаев использования...

В этом примере я создаю глобальную очередь отправки с одним экземпляром, что вы определенно сделали бы, скажем, если бы отслеживали события источника отправки из нескольких объектов; в этом случае каждый объект, прослушивающий очередь событий, может быть уведомлен, когда в очередь добавляется новая задача. Как только глобальная очередь установлена ​​(через queue_ref()), на нее можно ссылаться с помощью переменной queue в любом файле, в который включен заголовочный файл (примеры приведены ниже).

В одной из своих реализаций я вызывал queue_ref() в AppDelegate.m (main.c тоже подойдет). Таким образом, queue будет инициализирован до того, как любой другой вызывающий объект попытается получить к нему доступ. В остальных объектах я просто назвал queue. Возврат значения из переменной выполняется намного быстрее, чем вызов функции с последующей проверкой значения переменной перед ее возвратом.

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

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

Использовать:

  1. #include "GlobalQueue.h" в любом исходном файле реализации Objective-C или C.
  2. Позвоните queue_ref(), чтобы использовать очередь отправки. После вызова queue_ref() очередь может использоваться через переменную queue во всех исходных файлах.

Примеры:

Вызов 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()**);

Очередь звонков:

dispatch_queue_t serial_queue_with_queue_target = dispatch_queue_create_with_target("serial_queue_with_queue_target", DISPATCH_QUEUE_SERIAL, **queue**));]  
person James Bush    schedule 12.06.2021

Просто делать

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

который также работает в параллельной среде.

person Jason    schedule 30.04.2012
comment
Должно быть static Class * object = ...; return object;. - person alk; 07.09.2015