Google Analytics Mobile App 隨時掌握網站即時資訊

Google Analytics Mobile App 是由 Google 官方所提供的工具。

Android 的使用者可從以 Google Play 下載這個 Google Analytics Mobile App

iOS 的使用者可從以 Apple Store 下載這個 Google Analytics Mobile App

有了這個 Google Analytics Mobile App 就可以隨時掌握網站的即時數據。

哇!這各式各樣的圖,看起來好專業呀!

第一次進入 App 有簡單的介紹

左右滑動可以瀏覽不同維度的資料

凡是都要踏出第一步,勇敢地按下踏出第一步吧!

如果有好幾個網站的話,可以選擇看哪個網站的數據,之後就開始慢慢摸索這些數據吧~

看更多文章

Hexo 本機測試時如何關閉 Google Analytics

Python 寫藍芽 L2CAP 通訊程式

以下範例 ShengYu 將會介紹如何使用 Python 來寫 L2CAP 藍芽通訊程式,內容包含使用 bluetooth socket 建立一個連線,傳輸資料與斷線。

範例 l2cap-server.py 和 l2cap-client.py 簡單演示了如何使用 L2CAP 傳輸協定。

使用 L2CAP socket 幾乎與 RFCOMM socket 相同。

唯一不一樣的地方是在 BluetoothSocket 建構時傳入 L2CAP 這個參數, 並且 port number 為 0x1001 到 0x8FFF 之間的奇數, 而不是原本的 1 到 30。

預設的最大傳輸單元 MTU 為 672 bytes.

server 端程式

l2cap-server.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import bluetooth

server_sock=bluetooth.BluetoothSocket( bluetooth.L2CAP )

port = 0x1001
server_sock.bind(("",port))
server_sock.listen(1)

client_sock,address = server_sock.accept()
print "Accepted connection from ",address

data = client_sock.recv(1024)
print "received [%s]" % data

client_sock.close()
server_sock.close()

client 端程式

l2cap-client.py
1
2
3
4
5
6
7
8
9
10
11
12
import bluetooth

sock=bluetooth.BluetoothSocket( bluetooth.L2CAP )

bd_addr = "01:23:45:67:89:AB" # server 端的 addr
port = 0x1001

sock.connect((bd_addr, port))

sock.send("hello!!")

sock.close()

L2CAP 發送封包有最大限制,兩個裝置都各自維護了一個 MTU 來指定收到封包的最大大小,如果兩者調整各自的MTU,那麼它們有可能增加整個連線的MTU,從預設的 672 bytes 可以調整到 65535 bytes。但通常兩裝置的MTU值是設定相同的數值。在 PyBluez 裡,可以透過 set_l2cap_mtu 函式來調整這個值。

1
2
3
4
5
l2cap_sock = bluetooth.BluetoothSocket( bluetooth.L2CAP )
.
. # connect the socket
.
bluetooth.set_l2cap_mtu( l2cap_sock, 65535 )

set_l2cap_mtu 這函式使用方式相當地直觀,第一個參數為 L2CAP BluetoothSocket,第二個參數為欲設定的 MTU 數值。指定 Socket 的 incoming MTU 將會被調整,其它的 Socket 不受影響。set_l2cap_mtu 與其它 PyBluez 函式一樣,返回錯誤的話會拋出 BluetoothException 例外。

雖然我們先前提到使用 L2CAP 連線是不可靠,許多情況下有可能會用到,調整一個連線的可靠性在 PyBluez 也是很簡單呼叫個 set_packet_timeout 函式就完成了。

1
bluetooth.set_packet_timeout( bdaddr, timeout )

set_packet_timeout 帶入參數為 Bluetooth address 和一個 timeout 時間(單位為milliseconds),它將嘗試去調整封包的 timeout 時間(對任何 L2CAP 和 RFCOMM 的裝置連線)。 這個程序必須要使用管理員權限執行,且必須是一個主動連線。 只要有任何主動連線是打開的,這調整效果將一直持續,包含 Python 以外的程式。

參考
https://people.csail.mit.edu/albert/bluez-intro/x264.html

相關主題
Python 的第一支藍芽程式
Python 寫藍芽 RFCOMM 通訊程式
Python 寫藍芽 Service Discovery Protocol 通訊程式

Shell Script while 迴圈

常常在 shell script 腳本裡會需要讀檔案內容進來做字串處理,這大概是最常用到 while 的情形了,曾經有段時間需要一直處理文字檔裡的字串,不得不學會聰明的迴圈寫法,來解決重複性高的工作,以下就記錄這聰明的 while 迴圈寫法吧!

Read More

macbook pro retina 15 mid 2014 更換電池驚險記

本篇是我的 macbook pro retina 15 mid 2014 電池膨脹更換電池的紀錄。

下圖可以發現我的 MBPR 筆電電池已經膨脹到不行了,

由於我的 MBPR 筆電已經過保了,在原廠的維修報價驚人的情況下,只好另尋其他出路了,仿間的維修中心新聞事件很多令人感到害怕,加上對自己的手藝相當有信心,所以決定自己買電池來更換了!反正最好最壞打算即可。

MBPR 在換電池之前先幫電池做個檢查,使用 coconutBatterybattery health 兩個 App 雙重確認,兩個 App 都說電池是 good,品質也在 80% 以上,但我的電池早己膨脹,所以我覺得這種確認電池膨脹方式實際上是有點不靠譜的,還是得開背板確認有沒有膨脹比較準。

拆電池費了一番功夫終於拆下來了,MBPR 在拆電池時,我是採用網路上的小祕技:使用牙線。但我試過覺得手很痛,所以我後來改換成棉線就沒這麼痛。

新電池與舊的膨脹電池,看來這新電池應該不是原廠的,沒有標籤與QR Code,只是買之前我有跟店家確認是不是原廠的,店家說是,怎麼保證是原廠的這部分我是感到很好奇與疑問,

裝新電池上去準備撕輔助膜了

幸好最後裝上去可以開機,偵測得到有電池,使用 coconutBattery App 得知舊電池製造商為 dynapack(順達科技),我換上的新電池製造商為 simplo(新普),上網查一下原來兩家都是台灣的廠商呀!換電池還順便認識了蘋果供應鏈。

只好裝機 1~2 月開起來看看有沒有膨脹囉!

以下為電池保養小技巧

  • 平時不要把電力用到完全沒電關機,很傷電池續航力
  • 電量在 50% 左右最好充電
  • 電量用到 0% 很容易造成電池無法再充電,至少半個月給筆電充一次電

相關賣場資訊參考
蝦皮拍賣

Python 2to3 - 將 Python 2 程式碼轉為 Python 3 程式碼的工具

Python 2to3 它可以在命令行中使用 將 Python 2.x 程式碼轉換成 Python 3.x 版本的程式碼,

1
2to3 example.py

這個指令會印出和原始碼的區別。透過傳入 -w 參數,

1
2to3 -w example.py

2to3 也可以把需要的修改寫回到原檔案中(除非傳入了 -n 參數,否則會為原始檔案建立一個副本)。

其他參考
2to3 - 自動將 Python 2 代碼轉為 Python 3 代碼 — Python 3.8.0 文檔
https://docs.python.org/zh-cn/3/library/2to3.html

其它相關文章推薦
如果你想學習 Python 相關技術,可以參考看看下面的文章,
Python 新手入門教學懶人包
Python 讀檔,讀取 txt 文字檔
Python 字串分割 split
Python 取代字元或取代字串 replace
Python 產生 random 隨機不重複的數字 list
Python print 格式化輸出與排版
Python PIL 讀取圖片並顯示
Python OpenCV resize 圖片縮放

Qt5 的 View Model 學習

筆記一下 Qt5 的 View Model 幾個關鍵

Standard widgets 把 data 存在 widget 裡.
例如:QListWidget, QTableWidget, QTreeWidget.

View classes 是使用外部的 data (the model)
例如:QListView, QTableView, QTreeView.

單一數據存取

1
2
3
4
5
// 存
Item->setData(itemValue, Qt::UserRole); // 單一存取

// 取
ItemValue itemValue = (ItemValue)(index.data(Qt::UserRole).toInt());

自定義結構數據存取

定義數據類型

1
2
3
4
5
6
struct ItemData {
QString name;
QString id;
};

Q_DECLARE_METATYPE(ItemData)

1
2
3
4
5
6
// 存
Item->setData(QVariant::fromValue(itemData), Qt::UserRole+1); // 整體存取

// 取得
QVariant variant = index.data(Qt::UserRole+1);
ItemData itemData = variant.value<ItemData>();

參考
Qt5 Model/View Tutorial
Qt5 Model/View Programming
Qt 學習之路 2(41):model/view 架構| 上篇的部份中文翻譯
QT中的View Model模型| 上上篇的完整中文翻譯
Qt Model/View编程介绍| 上上上篇的完整中文翻譯 排版比較好
QT开发(三十六)——Model/View框架
Qt-Model/View原理与编程方法
Qt之QListView使用

其它相關文章推薦
[Qt] 讀檔,讀取 txt 文字檔
[Qt] 寫檔,寫入 txt 文字檔
安裝 Qt 在 Windows 7 (使用MSVC)
Qt產生的exe發布方式
Qt 新增多國語言.ts翻譯檔案
Qt5的中文亂碼問題如何解決

怎麼查詢 OpenCV 的版本

本篇將介紹如何查詢/查看自己電腦裡已安裝的 OpenCV 版本,
以下將列出兩種 OpenCV 版本查詢方式:

方法一:從 opencv source code 原始碼查看版本

目前官網 opencv 最新的版號是 4.5.0-pre,
https://github.com/opencv/opencv/blob/master/modules/core/include/opencv2/core/version.hpp
寫這篇文章時官網 opencv 的版號是 4.1.0-dev,
https://github.com/opencv/opencv/blob/64168fc20aa8a914cb5529f90ffac309854563b1/modules/core/include/opencv2/core/version.hpp
可以查看自己本機電腦裡當初載好的 OpenCV 原始碼是那一版的,
已我手中剛載好的原始碼為例,版本是 4.1.0-dev

modules/core/include/opencv2/core/version.hpp
1
2
3
4
5
6
7
8
...
#define CV_VERSION_MAJOR 4
#define CV_VERSION_MINOR 1
#define CV_VERSION_REVISION 0
#define CV_VERSION_STATUS "-dev"
...
#define CV_VERSION CVAUX_STR(CV_VERSION_MAJOR) "." CVAUX_STR(CV_VERSION_MINOR) "." CVAUX_STR(CV_VERSION_REVISION) CV_VERSION_STATUS
...

方法二:用 pkg-config 指令查看版本

只要你的 Linux 安裝好 OpenCV 就可以透過 pkg-config 指令查詢 OpenCV 的版本,
這是我之前裝的 3.4.8

1
2
$ pkg-config --modversion opencv
3.4.8

方法三:查看已安裝到系統的 header 檔

這方式是查看已安裝到系統的 version.hpp header 檔,檔案內容基本上跟方法一是一樣的,檔案路徑如下:

/usr/local/include/opencv2/core/version.hpp
1
2
3
4
5
6
7
8
...
#define CV_VERSION_MAJOR 4
#define CV_VERSION_MINOR 1
#define CV_VERSION_REVISION 0
#define CV_VERSION_STATUS "-dev"
...
#define CV_VERSION CVAUX_STR(CV_VERSION_MAJOR) "." CVAUX_STR(CV_VERSION_MINOR) "." CVAUX_STR(CV_VERSION_REVISION) CV_VERSION_STATUS
...

方法四:用 opencv_version 指令查看版本

使用 opencv_version 指令查詢已安裝好的 OpenCV 版本,
這是我之前裝的 3.4.8

1
2
$ opencv_version
3.4.8

其它相關文章推薦
OpenCV FileStorage 用法與 YAML 檔案讀取寫入範例
如何看OpenCV當初編譯的編譯參設定
OpenCV trace VideoCapture 流程

C++ 取得系統當前時間

本篇 ShengYu 將介紹如何使用 C++ 取得系統當前時間,
取得系統當前時間有幾種,本篇介紹使用 C++11 的 chrono 的寫法來取得系統當前時間。

範例1: 取得系統當下時間並轉成字串

使用時,需要先宣告引用 chrono

1
2
3
4
5
6
7
8
9
10
11
12
#include <chrono>
...
std::string getCurrentSystemTime() {
auto tt = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
struct tm* ptm = localtime(&tt);
char date[60] = { 0 };
sprintf(date, "%d-%02d-%02d-%02d.%02d.%02d",
(int)ptm->tm_year + 1900, (int)ptm->tm_mon + 1, (int)ptm->tm_mday,
(int)ptm->tm_hour, (int)ptm->tm_min, (int)ptm->tm_sec);

return std::string(date);
}

完整範例下載

使用 g++ 編譯 g++ main.cpp -std=c++11
輸出結果如下:

1
2019-05-17-20.56.58

範例2: 取得從 epoch 到現在經過多少微秒 milliseconds

1
2
unsigned long milliseconds_since_epoch =
std::chrono::system_clock::now().time_since_epoch() / std::chrono::milliseconds(1);

或者

1
2
unsigned long milliseconds_since_epoch = 
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();

參考
[1] c++ - Get time since epoch in milliseconds, preferably using C++11 chrono - Stack Overflow
https://stackoverflow.com/questions/16177295/get-time-since-epoch-in-milliseconds-preferably-using-c11-chrono

相關文章
C/C++ 新手入門教學懶人包
C++ 計算程式執行時間
std::deque 用法與範例
std::queue 用法與範例
std::vector 用法與範例
std::thread 怎麼實作的?
Python 取得系統當前時間

Linux rsync 同步最近修改的檔案/遠端同步資料檔案用法與範例

本篇介紹 Linux rsync 指令來同步最近修改的檔案到遠端機器去,
以下指令會先找出最近60天修改的檔案並同步到遠端機器上,

1
rsync `find . -name "*.pdf" -mtime -60` username@hostname:/tmp/

參考
https://www.linuxquestions.org/questions/linux-server-73/rsync-by-date-785692/

其它相關文章推薦
Linux 常用指令教學懶人包
Linux sed 字串取代用法與範例
Linux find 尋找檔案/尋找資料夾用法與範例
Linux cut 字串處理用法與範例
Linux tail 持續監看檔案輸出用法與範例
Linux grep/ack/ag 搜尋字串用法與範例
Linux tee 同時螢幕標準輸出和輸出到檔案用法與範例
Linux xargs 參數列表轉換用法與範例
Linux du 查詢硬碟剩餘空間/資料夾容量用法與範例
Linux wget 下載檔案用法與範例

C++ 設計模式 - 單例模式 Singleton Pattern

本篇介紹 c++ design pattern 的 singleton 單例模式,singleton 常被用來解決問題,許多時候整個系統只需要有一個的全域類別,這樣方便協調系統整體的行為。

一個類別在整個程式中只有一個實例,並且提供全域的存取,這就是單例模式 singleton pattern。

本文的目錄如下,

  • 什麼是單例?
  • 為什麼要用單例?
  • 餓漢模式 Eager Singleton
  • 懶漢模式 Lazy Singleton
  • 雙重鎖 Double-Checked Locking
  • Meyers Singleton (最簡單好用的懶漢模式,也是常見的實作方式)
  • 如何使用單例?
  • 誰也使用了單例模式 Singleton Pattern ?

那就開始本文吧!

什麼是單例?

單例是一種常用的軟體設計模式 design pattern,使用單例這個類別必須保證只能有一個實例 instance 存在。

為什麼要用單例?

有些物件只需要一個實例 instance,某段程式碼只能初始化一次,例如伺服器程式中通常會把設定檔資訊讀入到一個單例物件,之後使用單例 getInstance 然後就可以在任何地方(其他類別)獲得這些設定資訊,其他的類似應用情形還有使用者登入、資料庫連線、快取、與驅動程式溝通、執行緒池等等。

市面上實作 c++ singleton 有好幾種,這邊只提比較常用以及通用的方式,這邊就要開始講古了,
Singleton 分為兩種類型,分別為

  • 餓漢模式 Eager Singleton
  • 懶漢模式 Lazy Singleton

餓漢模式 Eager Singleton

這邊介紹的是餓漢模式,這版本會在進入 main 之前就將 Singleton 初始化好,原因是因為 static member variable 會被視為全域變數,

cpp-singleton-hungry.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// g++ cpp-singleton-hungry.cpp -std=c++11 -pthread
#include <iostream>
#include <thread>
using namespace std;

class Singleton {
public:
static Singleton& getInstance() {
cout << "Singleton getInstance\n";
this_thread::sleep_for(chrono::seconds(1));
return sInstance;
}

private:
Singleton() {
cout << "Singleton constructor\n";
}

static Singleton sInstance;
};

Singleton Singleton::sInstance;

int main() {
cout << "main" << endl;
thread t1([]{
cout << "singleton addr: " << &Singleton::getInstance() << endl;
});
cout << "singleton addr: " << &Singleton::getInstance() << endl;
t1.join();
return 0;
}

輸出如下,這邊故意在 getInstance() 裡 sleep 1 秒來測試多執行緒環境下也是沒問題的,

1
2
3
4
5
6
Singleton constructor
main
Singleton getInstance
Singleton getInstance
singleton addr: 0x6052a9
singleton addr: 0x6052a9

缺點是如果該 Singleton 初始化很耗時的話會佔用到進入 main 前的時間,
優點是在進入 main 之前就將 Singleton 初始化好,所以不會有 thread-safe 執行緒安全問題,

需要注意的是在多個 Singleton 有相依關係的話會產生問題,例如 SingletonA 和 SingletonB 都採用了餓漢模式,SingletonA 初始化時需要 SingletonB,而這兩個 instance 又在不同的編譯單元(cpp檔),那麼這兩個的初始化是不固定順序的,如果 SingletonA 在 SingletonB 之前初始化就會出錯。解決方式避開多個 Singleton 有相依關係的設計,或者採用懶漢模式。

懶漢模式 Lazy Singleton

以下篇幅都要來介紹懶漢模式,懶漢模式意思為要用到時才初始化,

範例如下,這邊 getInstance() 初始化完 instance 後改成回傳 reference 而不是 pointer,因為你總不希望別人不小心把你的 instance 給 free 了吧!而別人是無法 free 一個 reference 的,所以回傳 refernce 是比較好的作法。

cpp-singleton-lazy.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ...
class Singleton {
public:
static Singleton& getInstance() {
if (sInstance == nullptr) {
sInstance = new Singleton();
}
return *sInstance;
}

private:
Singleton() {}

static Singleton* sInstance;
};

Singleton* Singleton::sInstance = nullptr;
// ...

跟餓漢模式相比這種方法的好處是直到 getInstance() 被呼叫才會初始化,也稱為延遲初始化 (Lazy Initialization),這在一些初始化時消耗較大的情況有很大優勢。

而這個版本不是 thread-safe 的,如下列範例所示,假如現在有主執行緒和執行緒t1兩個執行緒都通過了 sInstance == nullptr 的判斷,那麼主執行緒和 t1 都會 new Singleton(),那麼就不是單例了。

cpp-singleton-lazy2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// g++ cpp-singleton-lazy2.cpp -std=c++11 -pthread
#include <iostream>
#include <thread>
using namespace std;

class Singleton {
public:
static Singleton& getInstance() {
cout << "Singleton getInstance\n";
if (sInstance == nullptr) {
this_thread::sleep_for(chrono::seconds(1));
sInstance = new Singleton();
}
return *sInstance;
}

private:
Singleton() {
cout << "Singleton constructor\n";
}

static Singleton* sInstance;
};

Singleton* Singleton::sInstance = nullptr;

int main() {
cout << "main" << endl;
thread t1([]{
cout << "singleton addr: " << &Singleton::getInstance() << endl;
});
cout << "singleton addr: " << &Singleton::getInstance() << endl;
t1.join();
return 0;
}

輸出如下,可以看出建構子初始化了兩次,印出來的記憶體位置也不一樣,

1
2
3
4
5
6
7
main
Singleton getInstance
Singleton getInstance
Singleton constructor
singleton addr: 0x138bda0
Singleton constructor
singleton addr: 0x7f3a040008c0

單例模式在多執行緒環境下需要特別小心。若多個執行緒同時呼叫getInstance(),可能會導致多個實例的建立。這時候可以使用雙重檢查鎖定(Double-Checked Locking)或是標準函式庫中的std::call_once來解決這個問題。

雙重檢測上鎖版 Double-Checked Locking

基於前一個懶漢模式例子,在多執行緒的情況下會有多次初始化實例的情形,所以很簡單的修改方式為加個鎖,就產生下面這樣的簡單上鎖版,

cpp-singleton-lock.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// g++ cpp-singleton-lock.cpp -std=c++11 -pthread
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

class Singleton {
public:
static Singleton& getInstance() {
cout << "Singleton getInstance\n";
lock_guard<std::mutex> lock(sMutex);
if (sInstance == nullptr) {
this_thread::sleep_for(chrono::seconds(1));
sInstance = new Singleton();
}
return *sInstance;
}

private:
Singleton() {
cout << "Singleton constructor\n";
}

static mutex sMutex;
static Singleton* sInstance;
};

mutex Singleton::sMutex;
Singleton* Singleton::sInstance = nullptr;

int main() {
cout << "main" << endl;
thread t1([]{
cout << "singleton addr: " << &Singleton::getInstance() << endl;
});
cout << "singleton addr: " << &Singleton::getInstance() << endl;
t1.join();
return 0;
}

輸出如下,即使在初始化前 sleep 1 秒也能順利的產生一個實體。

1
2
3
4
5
6
main
singleton addr: Singleton getInstance
singleton addr: Singleton getInstance
Singleton constructor
0x7ff47bc026f0
0x7ff47bc026f0

但是這樣的寫法在多執行緒的情況下很頻繁地呼叫 getInstance 會造成 race condition 問題,產生效能低落的情形,因此有了雙重檢測上鎖版 Double-Checked Locking 這個版本的改良。

由 Meyers 的 C++ and the Perils of Double-Checked Locking 這篇 paper 提出 Double-Checked Locking 版本,

cpp-singleton-lock2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// g++ cpp-singleton-lock2.cpp -std=c++11 -pthread
#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

class Singleton {
public:
static Singleton& getInstance() {
cout << "Singleton getInstance\n";
if (sInstance == nullptr) {
lock_guard<std::mutex> lock(sMutex);
if (sInstance == nullptr) {
sInstance = new Singleton();
}
}
return *sInstance;
}

private:
Singleton() {
cout << "Singleton constructor\n";
}

static mutex sMutex;
static Singleton* sInstance;
};

mutex Singleton::sMutex;
Singleton* Singleton::sInstance = nullptr;

int main() {
cout << "main" << endl;
thread t1([]{
cout << "singleton addr: " << &Singleton::getInstance() << endl;
});
cout << "singleton addr: " << &Singleton::getInstance() << endl;
t1.join();
return 0;
}

這邊會產生一個問題是當有一個執行緒在執行 sInstance = new Singleton();,有另外一個執行緒在檢查第一個 sInstance == nullptr 時很有可能出現問題,

因為 sInstance = new Singleton(); 這語句會分成三個步驟,
Step 1: Allocate memory to hold a Singleton object. 記憶體配置
Step 2: Construct a Singleton object in the allocated memory. 在己經配置的記憶體上建構 Singleton 物件
Step 3: Make sInstance point to the allocated memory. 將 sInstance 指向配置的記憶體

1
2
3
4
5
6
7
8
9
10
11
static Singleton& getInstance() {
if (sInstance == nullptr) {
lock_guard<std::mutex> lock(sMutex);
if (sInstance == nullptr) {
sInstance // Step 3
= operator new(sizeof(Singleton)); // Step 1
new(sInstance) Singleton; // Step 2
}
}
return *sInstance;
}

這順序很可能會變成 1->3->2,導致在1->3時 sInstance 已經不是 null,另外一個執行緒執行 sInstance == nullptr 時判斷 sInstance 已經有指向到有效的物件,就直接拿來使用,結果就會發生災難~

更多詳細內容與解決方式請看 Meyers 的 C++ and the Perils of Double-Checked Locking 這篇 paper 內容,這邊就不多做介紹。

有鑒於上面的各種坑,個人比較建議接下來介紹的這種方式。

Meyers Singleton (最簡單好用的懶漢模式,也是常見的實作方式)

在 Scott Meyers 大神的《Effective C++》書中條款 4 提出的 local static object 實作方式,也是屬於懶漢模式,利用 local static object 在函式第一次被呼叫使用才初始化的特性,這個作法在 C++11 以後是保證 thread-safe 的,Visual Studio 從 Visual Studio 2015 開始支援,GCC 從 GCC 4.3 開始支援
引用 C++11 standard 的 §6.7.4:

If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.

以下為 Meyers Singleton 的實作方式,

cpp-singleton-meyers.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// g++ cpp-singleton-meyers.cpp -std=c++11 -pthread
#include <iostream>
#include <thread>
using namespace std;

class Singleton {
public:
static Singleton& getInstance() {
static Singleton sInstance;
return sInstance;
}

private:
Singleton() {}
};

int main() {
cout << "main" << endl;
thread t1([]{
cout << "singleton addr: " << &Singleton::getInstance() << endl;
});
cout << "singleton addr: " << &Singleton::getInstance() << endl;
t1.join();
return 0;
}

很簡單吧!

如何使用單例?

剛剛前幾節著重在介紹 C++ 的 singleton 幾種作法,這邊要介紹如何使用單例以及實際上在使用時應該需要注意的部份,
在這篇 stackoverflow 討論到 constructor 必須是 private 的,避免被別人呼叫建構實例化,同樣地,copy constructor 複製建構子與 assignment operator 賦值運算子 (operator=) 也是比照辦理,

假設我有一個 Setting class 提供全域的存取,以下為 c++ singleton 單例模式的範例(Meyers Singleton 版本),

cpp-singleton.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// g++ cpp-singleton.cpp -std=c++11
#include <iostream>
using namespace std;

class Setting {
public:
static Setting& getInstance() {
static Setting instance;
return instance;
}

Setting(Setting const&) = delete;
// Setting s;
// Setting s2(s); // x

void operator=(Setting const&) = delete;
// Setting s2;
// s2 = s; // x

private:
Setting() {}
// Setting s; // x
};

int main() {
Setting &s = Setting::getInstance();
Setting &s2 = Setting::getInstance();
cout << "setting addr: " << &s << endl;
cout << "setting2 addr: " << &s2 << endl;

return 0;
}

輸出如下,

1
2
3
$ g++ singleton.cpp -std=c++11 && ./a.out 
setting addr: 0x10e6ff110
setting2 addr: 0x10e6ff110

誰也使用了單例模式 Singleton Pattern ?

誰也使用了 Singleton Pattern 單例模式? 我們來看看別人是怎麼使用的吧!
這邊舉個 OpenCV 內部的 VideoBackendRegistry class 為例,
使用 VideoBackendRegistry::getInstance() 來取得實例,所以在整個程式裡 VideoBackendRegistry 這個 class 就只會有一份實例,
https://github.com/opencv/opencv/blob/master/modules/videoio/src/videoio_registry.cpp

modules/videoio/src/videoio_registry.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class VideoBackendRegistry
{
protected:
VideoBackendRegistry()
{
// ...
}
// ...
public:
// ...
static VideoBackendRegistry& getInstance()
{
static VideoBackendRegistry g_instance;
return g_instance;
}
// ...
};

const std::vector<VideoBackendInfo> result = VideoBackendRegistry::getInstance().getAvailableBackends_CaptureByIndex();

在 OpenCV highgui 模組裡因應不同平台下有不同的實作方式,
在 Windows 平台下 window_w32.cpp 裡的 getInstance() 也是使用 static local 的方式,只是回傳的是 shared_ptr
https://github.com/opencv/opencv/blob/master/modules/highgui/src/window_w32.cpp

modules/highgui/src/window_w32.cpp
1
2
3
4
5
static std::shared_ptr<Win32BackendUI>& getInstance()
{
static std::shared_ptr<Win32BackendUI> g_instance = std::make_shared<Win32BackendUI>();
return g_instance;
}

AOSP (Android Open Source Project) 裡面 libutils 提供的 Singleton.h class 採用的是單一鎖方式,明顯地這種方式在多執行緒被頻繁呼叫的話會有 race condition 問題,2017 年在程式碼裡改成禁用,並建議改用 scoped static initialization 的方式,也就是本文提到的 Meyers Singleton 實作方式,
https://github.com/aosp-mirror/platform_system_core/blob/34a0e57a257f0081c672c9be0e87230762e677ca/libutils/include/utils/Singleton.h

libutils/include/utils/Singleton.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// DO NOT USE: Please use scoped static initialization. For instance:
// MyClass& getInstance() {
// static MyClass gInstance(...);
// return gInstance;
// }
template <typename TYPE>
class ANDROID_API Singleton
{
public:
static TYPE& getInstance() {
Mutex::Autolock _l(sLock);
TYPE* instance = sInstance;
if (instance == nullptr) {
instance = new TYPE();
sInstance = instance;
}
return *instance;
}
// ...
protected:
~Singleton() { }
Singleton() { }

private:
// ...
static Mutex sLock;
static TYPE* sInstance;
};

知名的 xbmc 專案(現已改名為 Kodi)也是採用 Meyers Singleton 實作方式
xbmc CPlayerCoreFactory::GetInstance()
https://github.com/xbmc/xbmc/blob/f621a026f671ab0f1a91d411f2f452915242bddf/xbmc/cores/playercorefactory/PlayerCoreFactory.cpp

xbmc/cores/playercorefactory/PlayerCoreFactory.cpp
1
2
3
4
5
CPlayerCoreFactory& CPlayerCoreFactory::GetInstance()
{
static CPlayerCoreFactory sPlayerCoreFactory;
return sPlayerCoreFactory;
}

參考
單例模式 - wikipedia
https://zh.wikipedia.org/wiki/%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F
[ Day 5 ] 初探設計模式 - 單例模式 (Singleton)
https://ithelp.ithome.com.tw/articles/10203092
Java單例模式——並非看起來那麼簡單
https://blog.csdn.net/goodlixueyong/article/details/51935526
Singleton class and correct way to access it in C++
https://codereview.stackexchange.com/questions/197486/singleton-class-and-correct-way-to-access-it-in-c
So Singletons are bad, then what?
https://softwareengineering.stackexchange.com/questions/40373/so-singletons-are-bad-then-what/40374#40374
It’s important to distinguish here between single instances and the Singleton design pattern.
C++ Singleton design pattern
https://stackoverflow.com/questions/1008019/c-singleton-design-pattern/1008289#1008289
Singleton pattern in C++
https://stackoverflow.com/questions/2496918/singleton-pattern-in-c

其它相關文章推薦
C/C++ 新手入門教學懶人包