首頁 > 軟體

C++特殊類設計概念與範例講解

2023-09-05 18:00:06

一、設計模式概念

設計模式是一套被反覆使用、多數人知曉的、經過分類的、程式碼設計經驗的總結。

使用設計模式的目的:為了程式碼可重用性、讓程式碼更容易被他人理解、保證程式碼可靠性。

根本原因是為了程式碼複用,增加可維護性。

設計模式的例子:迭代器模式

二、設計一個不能被拷貝的類

拷貝一共就只有兩個場景,一個是拷貝構造,一個是賦值運運算元過載。所以我們想要設計出一個不能被拷貝的類只需要讓外部無法呼叫這兩個函數即可。

在C++98中,我們的方法是將拷貝構造和賦值運運算元過載只宣告不定義並且將許可權設定為私有。

class anti_copy
{
public:
	anti_copy()
	{}
private:
	anti_copy(const anti_copy& ac);
	anti_copy& operator=(const anti_copy& ac);
};

設計原因:

1️⃣ 私有:如果宣告成共有,那麼就可以在類外面實現定義。

2️⃣ 只宣告不定義:因為如果不宣告編譯器會預設生成這兩個的預設成員函數。而不定義是因為該函數不會被呼叫,就不用寫了,這樣編譯的時候就會出現連結錯誤。

而在C++11中引入了關鍵字——delete。

如果在預設成員函數後跟上=delete,表示讓編譯器刪除掉該預設成員函數。即使許可權是共有也無法呼叫已刪除的函數。

class anti_copy
{
public:
	anti_copy()
	{}
	anti_copy(const anti_copy& ac) = delete;
	anti_copy& operator=(const anti_copy& ac) = delete;
private:
};

三、設計一個只能在堆上建立物件的類

3.1 私有構造

首先要把建構函式給私有,不然這個類就可以在任意位置被建立。而建構函式被私有了以後我們怎麼建立物件呢?

我們可以在定義一個成員函數,讓這個函數在堆上申請空間,但我們知道必須現有物件才能呼叫成員函數。所以我們就把這個函數設定成靜態成員函數。

class OnlyHeap
{
public:
	static OnlyHeap* GetObj()
	{
		return new OnlyHeap;
	}
private:
	OnlyHeap()
	{}
};

但是這樣也不完全對,如果我們這麼寫:

class OnlyHeap
{
public:
	static OnlyHeap* GetObj()
	{
		return new OnlyHeap;
	}
private:
	OnlyHeap()
	{}
};
int main()
{
	OnlyHeap* hp1 = OnlyHeap::GetObj();
	OnlyHeap hp2(*hp1);
	return 0;
}

這裡的hp2就是棧上的物件。所以我們也要把拷貝構造給封住。

class OnlyHeap
{
public:
	static OnlyHeap* GetObj()
	{
		return new OnlyHeap;
	}
	OnlyHeap(const OnlyHeap& hp) = delete;
private:
	OnlyHeap()
	{}
};

3.2 私有解構

class OnlyHeap
{
public:
	OnlyHeap()
	{}
	OnlyHeap(const OnlyHeap& hp) = delete;
private:
	~OnlyHeap()
	{}
};
int main()
{
	OnlyHeap hp1;// error
	OnlyHeap* hp2 = new OnlyHeap;
	return 0;
}

這裡的hp1就不能建立成功,因為物件銷燬的時候會呼叫解構函式,但是這裡的解構是私有的,所以該物件無法呼叫。

但是我們要銷燬hp2該怎麼辦呢?

我們可以定義一個成員函數顯示呼叫解構函式。

class OnlyHeap
{
public:
	OnlyHeap()
	{}
	OnlyHeap(const OnlyHeap& hp) = delete;
	void Destroy()
	{
		this->~OnlyHeap();
	}
private:
	~OnlyHeap()
	{}
};
int main()
{
	OnlyHeap* hp2 = new OnlyHeap;
	hp2->Destroy();
	return 0;
}

四、設計一個只能在棧上建立物件的類

為了不讓這個類隨便定義出物件,首先要把建構函式私有。然後跟上面只能在堆上建立物件的方法相似,定義出一個靜態成員函數返回棧上建立的物件。

class StackOnly
{
public:
	static StackOnly GetObj()
	{
		return StackOnly();
	}
private:
	StackOnly()
	{}
};
int main()
{
	StackOnly hp = StackOnly::GetObj();
	return 0;
}

但是這裡有一個問題,無法防止建立靜態物件:

static StackOnly hp2 = StackOnly::GetObj();

五、設計不能被繼承的類

在C++98,為了不讓子類繼承,我們可以把建構函式私有化,因為子類需要先呼叫父類別的建構函式初始化父類別的那一部分成員。

class NoInherit
{
public:
private:
	NoInherit()
	{}
};

而在C++11中引入的新的關鍵字final,被final關鍵字修飾的類不能被繼承。

class NoInherit final
{
public:
private:
};

六、單例模式

一個類只能建立一個物件,即單例模式,該模式可以保證系統中該類只有一個範例,並提供一個存取它的全域性存取點,該範例被所有程式模組共用。

單例模式的特點就是全域性只有一個唯一物件。

6.1 餓漢模式

怎麼能做到全域性只是用一個物件呢,比方說我們現在想要實現一個英漢字典,首先我們要把建構函式私有,不然無法阻止建立物件。然後我們可以在類裡面定義一個自己型別的靜態成員變數,作用域是全域性的。因為對比定義在外邊的靜態成員變數,內部的可以呼叫建構函式。

這裡要注意把拷貝也要封住。

class Singleton
{
public:
	static Singleton& GetObj()
	{
		return _s;
	}
	void insert(const std::string& s1, const std::string& s2)
	{
		_dict[s1] = s2;
	}
	void Print()
	{
		for (auto& e : _dict)
		{
			cout << e.first << "->" << e.second << endl;
		}
	}
	// 防拷貝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	Singleton()
	{}
	std::map<std::string, std::string> _dict;
private:
	static Singleton _s;// 宣告
};
Singleton Singleton::_s;// 定義
int main()
{
	Singleton::GetObj().insert("corn", "玉米");
	Singleton& dic1 = Singleton::GetObj();
	dic1.insert("apple", "蘋果");
	dic1.insert("banana", "香蕉");
	Singleton& dic2 = Singleton::GetObj();
	dic2.insert("pear", "梨");
	dic2.Print();
	return 0;
}

餓漢模式有什麼特點呢?

它會在一開始(main之前)就建立物件。

餓漢模式有什麼缺點呢?

1️⃣ 如果單例物件構造十分耗時或者佔用很多資源,比如載入外掛啊, 初始化網路連線啊,讀取檔案啊等等,而有可能該物件程式執行時不會用到,那麼也要在程式一開始就進行初始化,就會導致程式啟動時非常的緩慢。

2️⃣ 多個單例類之間如果有依賴關係餓漢模式就無法控制,比方說要求A類初始化時必須呼叫B,但是餓漢無法控制先後順序。

所以針對這些問題,就有了懶漢模式。

6.2 懶漢模式

第一次使用範例物件時,建立物件(用的時候建立)。程序啟動無負載。多個單例範例啟動順序自由控制。

我們可以直接對上面餓漢模式的程式碼進行修改,把靜態成員變數變成指標。然後把獲取的函數改變一下:

static Singleton& GetObj()
	{
		// 第一次呼叫才會建立物件
		if (_s == nullptr)
		{
			_s = new Singleton;
		}
		return *_s;
	}

整體程式碼:

class Singleton
{
public:
	static Singleton& GetObj()
	{
		// 第一次呼叫才會建立物件
		if (_s == nullptr)
		{
			_s = new Singleton;
		}
		return *_s;
	}
	void insert(const std::string& s1, const std::string& s2)
	{
		_dict[s1] = s2;
	}
	void Print()
	{
		for (auto& e : _dict)
		{
			cout << e.first << "->" << e.second << endl;
		}
	}
	// 防拷貝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	Singleton()
	{}
	std::map<std::string, std::string> _dict;
private:
	static Singleton* _s;// 宣告
};
Singleton* Singleton::_s = nullptr;// 定義

6.2.1 執行緒安全問題

上面的程式碼存在問題,當多個執行緒同時呼叫GetObj(),就會建立多個物件。所以為了執行緒安全我們要加鎖。為了保證鎖自動銷燬,我們可以自定義一個鎖。

template <class Lock>
class LockAuto
{
public:
	LockAuto(Lock& lk)
		: _lk(lk)
	{
		_lk.lock();
	}
	~LockAuto()
	{
		_lk.unlock();
	}
private:
	Lock& _lk;
};
class Singleton
{
public:
	static Singleton& GetObj()
	{
		// 第一次呼叫才會建立物件
		if (_s == nullptr)// 只有第一次才用加鎖
		{
			LockAuto<mutex> lock(_mutex);
			if (_s == nullptr)
			{
				_s = new Singleton;
			}
		}
		return *_s;
	}
	void insert(const std::string& s1, const std::string& s2)
	{
		_dict[s1] = s2;
	}
	void Print()
	{
		for (auto& e : _dict)
		{
			cout << e.first << "->" << e.second << endl;
		}
	}
	// 防拷貝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	Singleton()
	{}
	std::map<std::string, std::string> _dict;
private:
	static Singleton* _s;// 宣告
	static mutex _mutex;// 鎖
};
Singleton* Singleton::_s = nullptr;// 定義
mutex Singleton::_mutex;// 定義

6.2.2 新寫法

class Singleton
{
public:
	static Singleton& GetObj()
	{
		static Singleton dic;
		return dic;
	}
	void insert(const std::string& s1, const std::string& s2)
	{
		_dict[s1] = s2;
	}
	void Print()
	{
		for (auto& e : _dict)
		{
			cout << e.first << "->" << e.second << endl;
		}
	}
	// 防拷貝
	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
private:
	Singleton()
	{}
	std::map<std::string, std::string> _dict;
};

這裡就用了靜態區域性變數只會在第一次定義的時候初始化。在C++11之前是不能保證執行緒安全的,但是C++11之後就可以了。

到此這篇關於C++特殊類設計概念與範例講解的文章就介紹到這了,更多相關C++特殊類設計內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com