沒有學不會的C++:為什麼不要使用全域性變數
在寫程式時,我們都知道一條規範:不要使用全域性變數。至於為什麼,有可能是因為它會汙染名稱空間,也有可能是因為它會造成程式的不確定性,本文主要使用一個例子,來說明全域性變數是如何讓程式變得不確定的。
我們先定義兩個類,一個Cat
,一個Dog
,如下是cat.h
和cat.cc
檔案
// cat.h #include <iostream> using namespace std; class Cat; extern Cat c; class Cat { public: Cat(char* name); void meow(); private: char* _name; }; // cat.cc #include "cat.h" #include "dog.h" Cat c("mimi"); Cat::Cat(char* name) { cout << "construct cat" << endl; _name = name; } void Cat::meow() { cout << "cat " << _name << " meow" << endl; }
Cat
類很簡單,只有一個成員_name
,它是一個指標變數,且通過meow
方法把它列印到螢幕上,同時,我們還定義了一個全域性變數Cat c("mimi");
,同樣的,Dog
類的定義也很簡單,如下:
// dog.h #include <iostream> using namespace std; class Dog; extern Dog d; class Dog { public: Dog(char* name); void bark(); private: char* _name; }; // dog.cc #include "dog.h" #include "cat.h" Dog d("kobe"); Dog::Dog(char* name) { cout << "construct dog" << endl; _name = name; } void Dog::bark() { cout << "dog " << _name << " bark" << endl; }
我們給Dog
也定義了一個全域性變數d("kobe");
,此時,我們修改一下Cat
的建構函式,在裡面引用全域性變數d
,看看會發生什麼
Cat::Cat(char* name) { cout << "construct cat" << endl; _name = name; d.bark(); }
編譯執行前,別忘了我們的入口檔案main.cc
,如下
int main() { return 0; }
現在,我們將其進行編譯執行
... g++ -o main main.o cat.o dog.o -std=c++11// 編譯時的輸出,說明了連結順序 $ ./main construct cat // [1]50759 segmentation fault./main
可以看到程式崩潰了,崩潰原因是我們剛才在Cat
建構函式中增加的一行呼叫全域性變數的程式碼,因為在呼叫這行程式碼時,全域性變數d
(實際上是d._name
)還沒初始化。
而全域性變數的初始化順序是由編譯器決定的,所以如果我們的全域性變數間又有互相依賴的話,就很容易造成程式崩潰。
避免使用全域性變數的方法也有很多,其中最廣泛的應數 Singleton 模式了,針對上面的程式碼,我們可以定義一個Singleton
類,其中包含Cat
和Dog
的靜態指標,如下
// singleton.h class Cat; class Dog; class Singleton { static Dog* pd; static Cat* pc; public: ~Singleton(); static Dog* getDog(); static Cat* getCat(); };
與此同時,我們還聲明瞭兩個靜態方法,用來獲取Dog
或Cat
的指標,並且,我們希望Dog
和Cat
是以 lazy 的方式進行初始化的,即下面的 singleton.cc 檔案的實現
// singleton.cc #include "singleton.h" #include "dog.h" #include "cat.h" Dog* Singleton::pd = 0; Cat* Singleton::pc = 0; Singleton::~Singleton() { delete pd; delete pc; pd = 0; pc = 0; } Dog* Singleton::getDog() { if (pd == 0) { pd = new Dog("kobe"); } return pd; } Cat* Singleton::getCat() { if (pc == 0) { pc = new Cat("mimi"); } return pc; }
可以看到初始化Cat
和Dog
的時機是在第一次呼叫getCat
和getDog
時。現在你就可以刪掉程式中的全域性變量了,當你需要使用Cat
物件或Dog
物件時,直接呼叫Singleton::getCat()
或Singleton::getDog()
即可。
參考:
- ofollow,noindex">https://www.youtube.com/watch?v=hE77OSTE2J0&t=87s