如何在C中创建单例?

问题描述 投票:0回答:7

在 C 中创建单例的最佳方法是什么?并发解决方案会很好。

我知道 C 并不是您用于单例的第一种语言。

c design-patterns singleton
7个回答
36
投票

首先,C 不适合 OO 编程。如果你这样做的话,你会一路战斗。其次,单例只是进行了一些封装的静态变量。所以你可以使用静态全局变量。然而,全局变量通常有太多与之相关的弊病。否则,您可以使用函数局部静态变量,如下所示:

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

最后,请记住,单身人士大多受到虐待。很难让它们正确,尤其是在多线程环境下......


20
投票

你不需要。 C 已经有了全局变量,因此您不需要解决方法来模拟它们。


19
投票

它与 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;
};

请注意,您还需要一个函数来释放单例。特别是如果它获取进程退出时未自动释放的任何系统资源。


5
投票

编辑:我的答案假设您正在创建的单例有点复杂,并且有一个多步骤的创建过程。如果只是静态数据,请像其他人建议的那样使用全局数据。

C 中的单例会很奇怪。 。 。我从来没有见过一个“面向对象的C”的例子看起来特别优雅。如果可能,请考虑使用 C++。 C++ 允许你选择你想要使用的功能,很多人只是把它当作“更好的 C”。

下面是一种非常典型的无锁一次性初始化模式。如果前一个值为空,InterlockCompareExchangePtr 将自动交换新值。这可以保护如果多个线程尝试同时创建单例,则只有一个线程会获胜。其他人将删除他们新创建的对象。

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

3
投票

这是另一个观点:C 程序中的每个文件实际上都是一个单例类,在运行时自动实例化并且不能被子类化。

  • 全局静态变量是你的私有类成员。
  • 全局非静态是公共的(只需在某些头文件中使用
    extern
    声明它们)。
  • 静态函数是私有方法
  • 非静态函数是公共函数。

为所有内容指定正确的前缀,现在您可以使用

my_singleton_method()
代替
my_singleton.method()

如果您的单例很复杂,您可以编写一个

generate_singleton()
方法在使用前对其进行初始化,但是您需要确保所有其他公共方法检查它是否被调用,如果没有则出错。


0
投票

我认为这个解决方案对于大多数用例来说可能是最简单、最好的......

在这个例子中,我正在创建一个单实例全局调度队列,如果您正在跟踪来自多个对象的调度源事件,那么您肯定会这样做;在这种情况下,当新任务添加到队列中时,侦听事件队列的每个对象都可以收到通知。一旦设置了全局队列(通过

queue_ref()
),就可以在包含头文件的任何文件中使用
queue
变量来引用它(下面提供了示例)。

在我的一个实现中,我在 AppDelegate.m 中调用了

queue_ref()
(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**));]  

-3
投票

就做吧

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

它也可以在并发环境中工作。

© www.soinside.com 2019 - 2024. All rights reserved.