如何在C中创建Singleton?

时间:2009-04-29 18:37:44

标签: c design-patterns singleton

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

我知道C不是你用于单身人士的第一种语言。

7 个答案:

答案 0 :(得分:29)

首先,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;
}

最后,请记住,单身人士大多被滥用。很难让它们正确,特别是在多线程环境下......

答案 1 :(得分:18)

你不需要。 C已经有全局变量,所以你不需要解决它们来模拟它们。

答案 2 :(得分:14)

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

请注意,您还需要一个功能来释放单身人士。特别是如果它抓取了在进程退出时未自动释放的任何系统资源。

答案 3 :(得分:5)

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

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

下面是一个非常典型的无锁一次性初始化模式。如果前一个为null,则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());

答案 4 :(得分:2)

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

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

为所有内容添加正确的前缀,现在您可以使用my_singleton_method()代替my_singleton.method()

如果你的单例是复杂的,你可以在使用之前编写一个generate_singleton()方法来初始化它,但是你需要确保所有其他的公共方法检查它是否被调用,如果没有则错误输出。

答案 5 :(得分: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**));]  

答案 6 :(得分:-2)

只做

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

也适用于并发环境。