意味
シングルトン モードの本来の目的は、ランタイムおよびランタイム空間全体に特定のデータ型の一意のインスタンスが 1 つだけ存在することを保証し、グローバル アクセス インターフェイスを提供することです。
インスタンスの作成とアクセスの 2 つの観点から、シングルトン パターンをより深く理解できます: (1) データ型にはインスタンスが 1 つだけあり、プログラマは通常のデータ型のようにこの型のインスタンスを自由に定義できません。これは、型のインスタンスの定義と作成を制限する去勢されたデータ型です。(2) このアクセスインターフェースは、グローバルにユニークなインスタンスのアクセスインターフェースであり、通常の意味でのデータアクセスインターフェースではありません。
達成
一般的なシングルトンモードは、オブジェクトの生成と呼び出しのタイミング関係によってレイジータイプとハングリータイプに分けられます。通常、Hungry 型はプログラムの開始時にオブジェクトを作成し、遅延初期化されません。Lazy 型は、実際に使用されるときに遅延初期化を使用して作成されます。
ハングリースタイル
Hungry スタイルは、Hungry と同様に、プログラムの開始時に、必要かどうかに関係なく作成されます。C++ では、Hungry スタイルを実装するためにグローバル変数として宣言するのが一般的で、グローバル変数は main 関数の実行前にグローバル変数オブジェクトを作成し、main 関数の実行終了後にグローバル変数を解放します。
実行可能プログラムにはグローバル _CTOR_LIST 関数ポインタ配列があり、コンパイラはリンク時にグローバル変数のコンストラクタ ポインタを _CTOR_LIST に追加します。その後、実行可能プログラムは main 関数を実行する前に _CTOR_LIST をトラバースして実行します。すべての関数ポインタは、したがってグローバル変数の構築が完了しました。
同じ実行可能プログラムにはグローバル _DTOR_LIST 関数ポインタ配列もあり、コンパイラはリンク時にグローバル変数のデストラクタ ポインタを _DTOR_LIST に追加します。実行可能プログラムは main 関数を実行した後、この _DTOR_LIST すべての関数ポインタを走査して実行します。これでグローバル変数の破棄が完了します。
C++ の飢えた中国語スタイルのグローバル変数の構築プロセスに精通しているので、C 言語の飢えた中国語スタイルを実現するためにグローバル変数の原則の構築原理を参照します。幸いなことに、GCC と MSVC はどちらも、main の前後で関数を呼び出すための対応するメカニズムを提供します。
GCC
GCC では、属性キーワードを使用してコンストラクターおよびデストラクター C 関数を宣言できます。コンストラクターとして宣言された関数は main の前に呼び出され、デストラクターとして宣言された関数は main の後に呼び出されます。
#include<stdio.h>
// 声明一个constructor属性函数,此函数会在main之前运行
__attribute__((constructor)) void before()
{
printf("run function before main\n");
}
// 声明一个destructor属性函数,此函数会在main之后运行
__attribute__((destructor)) void after()
{
printf("run function after main\n");
}
int main(int argc, char **argv)
{
printf("run main function\n");
return 0;
}
GCC メイン関数の前に関数を実行するという原則を参照すると、GCC バージョンのシングルトン バージョンを実装できます。
シングルトン宣言
#pragma once
#include <stdio.h>
#include <stdlib.h>
typedef void File;
typedef enum BOOL
{
FALSE = 0,
TRUE = 1,
}BOOL;
typedef struct tagFileManager
{
File* (*mkFile)(const char* fileName, char const* mode);
BOOL (*rmFile)(const char* fileName);
int (*write)(File* file, const char* buf, int size);
BOOL (*exit)(const char* fileName);
BOOL (*close)(const File* file);
}FileManager;
シングルトンの実装
#include "file_manager.h"
#include <io.h>
static FileManager g_fileManager;
typedef int constructor();
static File* mkFile(const char* fileName, char const* mode);
static BOOL rmFile(const char* fileName);
static int fileWrite(File* file, const char* buf, int size);
static BOOL isExit(const char* fileName);
static BOOL fileClose(const File* file);
File* mkFile(const char* fileName, char const* mode)
{
FILE* file = NULL;
if (0 == fopen_s(&file, fileName, mode))
{
return file;
}
return NULL;
}
BOOL rmFile(const char* fileName)
{
if (isExit(fileName))
{
return !remove(fileName);
}
}
int fileWrite(File* file, const char* buf, int size)
{
return fwrite(buf, size, 1, file);
}
BOOL isExit(const char* fileName)
{
return (_access(fileName, 0) == 0);
}
BOOL fileClose(const File* file)
{
return !fclose(file);
}
__attribute__((constructor)) static int ctor()
{
g_fileManager.exit = isExit;
g_fileManager.mkFile = mkFile;
g_fileManager.rmFile = rmFile;
g_fileManager.write = fileWrite;
g_fileManager.close = fileClose;
return 0;
}
__attribute__((destructor)) static int dtor()
{
g_fileManager.exit = NULL;
g_fileManager.mkFile = NULL;
g_fileManager.rmFile = NULL;
g_fileManager.write = NULL;
g_fileManager.close = NULL;
return 0;
}
FileManager* fileManager()
{
return &g_fileManager;
}
MSVC
MSVC はコード セグメント名を宣言することによって実装します。2 つの特別なセグメント名「.CRT XIU」、「.CRT XIU」、「.CRT」を宣言します。XIU 」、_ _" .C R T XCU" を指定すると、リンカは".CRT XIU" セクションで宣言された関数を "C 初期化関数テーブル" に配置し、また、".CRT XIU" セクションで宣言された関数を" C初期化関数テーブル」でも「.CRT」で宣言されます。「X I U 」セクションの関数は「C初期化関数テーブル」に配置され、 「. C R T XCU 」セクションで宣言された関数も「C++ 初期化関数テーブル」に配置されます。MSVC実行可能プログラムは、最初にmain 関数を実行する前に、「C 初期化関数テーブル」と「C++ 初期化関数テーブル」を走査し、関数を順番に実行します。main 関数の実行後、_onexit() を通じて登録する必要があります。
#include <stdlib.h>
int before1()
{
printf("run function before1 before main\n");
return 0;
}
int before2()
{
printf("run function before2 before main\n");
return 0;
}
int after()
{
printf("run function after main\n");
return 0;
}
typedef int constructor();
#pragma data_seg(".CRT$XIU")
static constructor *beforeTab1[] = {
before1};
#pragma data_seg(".CRT$XCU")
static constructor *beforeTab2[] = {
before2};
#pragma data_seg()
int _tmain(int argc, _TCHAR *argv[])
{
_onexit(after);
printf("run main function\n");
return 0;
}
MSVC メイン関数の前後で関数を実行する原理分析に基づいて、MSVC バージョンのハングリー シングルトン モードを作成できます。
シングルトン宣言
#pragma once
#include <stdio.h>
#include <stdlib.h>
typedef void File;
typedef enum BOOL
{
FALSE = 0,
TRUE = 1,
}BOOL;
typedef struct tagFileManager
{
File* (*mkFile)(const char* fileName, char const* mode);
BOOL (*rmFile)(const char* fileName);
int (*write)(File* file, const char* buf, int size);
BOOL (*exit)(const char* fileName);
BOOL (*close)(const File* file);
}FileManager;
FileManager* fileManager();
シングルトンの実装
#include "file_manager.h"
#include <io.h>
static FileManager g_fileManager;
typedef int constructor();
static File* mkFile(const char* fileName, char const* mode);
static BOOL rmFile(const char* fileName);
static int fileWrite(File* file, const char* buf, int size);
static BOOL isExit(const char* fileName);
static BOOL fileClose(const File* file);
static int ctor();
static int dtor();
File* mkFile(const char* fileName, char const* mode)
{
FILE* file = NULL;
if (0 == fopen_s(&file, fileName, mode))
{
return file;
}
return NULL;
}
BOOL rmFile(const char* fileName)
{
if (isExit(fileName))
{
return !remove(fileName);
}
}
int fileWrite(File* file, const char* buf, int size)
{
return fwrite(buf, size, 1, file);
}
BOOL isExit(const char* fileName)
{
return (_access(fileName, 0) == 0);
}
BOOL fileClose(const File* file)
{
return !fclose(file);
}
static int ctor()
{
g_fileManager.exit = isExit;
g_fileManager.mkFile = mkFile;
g_fileManager.rmFile = rmFile;
g_fileManager.write = fileWrite;
g_fileManager.close = fileClose;
_onexit(dtor);
return 0;
}
static int dtor()
{
g_fileManager.exit = NULL;
g_fileManager.mkFile = NULL;
g_fileManager.rmFile = NULL;
g_fileManager.write = NULL;
g_fileManager.close = NULL;
return 0;
}
#pragma data_seg(".CRT$XIU")
static constructor * before[] = {
ctor };
#pragma data_seg()
FileManager* fileManager()
{
return &g_fileManager;
}
要約する
シングルトン モードは、ランタイムおよびランタイム空間全体に特定のデータ型の一意のインスタンスが 1 つだけ存在することを保証し、グローバル アクセス インターフェイスを提供します。この記事では、シングルトンの目的とシングルトンのハングリー実装について紹介します。次の単元では、引き続きシングルトンの遅延実装について説明します。