muduo網路庫學習筆記(0):物件導向程式設計風格和基於物件程式設計風格的比較
我們通過對一個執行緒類的封裝來比較這二者的不同。
物件導向程式設計風格
使用物件導向程式設計風格封裝執行緒類,一般這麼做:寫一個Thread base class,含有(純)虛擬函式Thread::run(),然後應用程式派生一個derived class,覆寫run()。程式裡的每一種執行緒對應一個Thread的派生類。
類圖如下:
程式碼如下:
檔名:Thread.h -- 標頭檔案
#ifndef _THREAD_H_
#define _THREAD_H_
#include <pthread.h>
// 執行緒類,是一個抽象基類
class Thread
{
public:
Thread(); // 建構函式
virtual ~Thread(); // 基類的解構函式宣告為虛解構函式,保證派生類的解構函式會被呼叫
void Start(); // 建立執行緒
void Join(); // 等待執行緒結束
void SetAutoDelete(bool autoDelete); // 設定自動銷燬標識
private:
static void* ThreadRoutine(void* arg); // 執行緒的入口函式
virtual void Run() = 0; // 真正的執行緒執行體,宣告為純虛擬函式,作為一個介面,不同的派生類有不同的實現
pthread_t threadId_; // 執行緒ID
bool autoDelete_; // 自動銷燬標識
};
#endif // _THREAD_H_
檔名:Thread.cpp -- 原始檔
#include "Thread.h"
#include <iostream>
using namespace std;
// 建構函式
Thread::Thread() : autoDelete_(false)
{
cout << "Thread ..." << endl;
}
// 解構函式
Thread::~Thread()
{
cout << "~Thread ..." << endl;
}
// 執行緒建立函式,呼叫pthread_create()
void Thread::Start()
{
pthread_create(&threadId_, NULL, ThreadRoutine, this); // 將this指標作為引數傳遞給執行緒執行函式
}
// 等待執行緒結束函式,呼叫pthread_join()
void Thread::Join()
{
pthread_join(threadId_, NULL);
}
// 執行緒的入口函式,宣告為靜態,可通過類名直接呼叫
void* Thread::ThreadRoutine(void* arg)
{
Thread* thread = static_cast<Thread*>(arg); // pthread_create()傳遞的arg即this指標,指向派生類物件
thread->Run(); // 基類指標呼叫派生類所實現的虛擬函式,用到了虛擬函式的多型
if (thread->autoDelete_)
delete thread;
return NULL;
}
// 設定自動銷燬標識函式
void Thread::SetAutoDelete(bool autoDelete)
{
autoDelete_ = autoDelete;
}
檔名:Thread_test.cpp -- 測試:實現派生類TestThread,繼承自Thread基類
#include "Thread.h"
#include <unistd.h>
#include <iostream>
using namespace std;
class TestThread : public Thread
{
public:
TestThread(int count) : count_(count)
{
cout << "TestThread ..." << endl;
}
~TestThread()
{
cout << "~TestThread ..." << endl;
}
private:
// 派生類的執行緒執行方法
void Run()
{
while(count_--)
{
cout << "this is a test ..." << endl;
sleep(1);
}
}
int count_;
};
int main(void)
{
TestThread* t2 = new TestThread(5);
t2->SetAutoDelete(true); // 設定自動銷燬標識,執行緒執行完畢,執行緒物件自動銷燬
t2->Start();
t2->Join();
for ( ; ; )
pause();
return 0;
}
程式說明:
1.Thread類作為抽象類,是不能例項化物件的。TestThread類是派生類,是具體類。
2.因為Thread類作為多型基類,該類的解構函式必須是一個純虛解構函式,否則會導致派生類的資源釋放不完全。
3.執行緒的相關函式:
包含標頭檔案:#include <pthread.h>
// 建立執行緒
int pthread_create(pthread_t *tidp, const pthread_attr_t *attr,
(void*)(*start_rtn)(void*), void *arg);
引數:
第一個引數為指向執行緒識別符號的指標;
第二個引數用來設定執行緒屬性;
第三個引數是執行緒執行函式的起始地址;
第四個引數是執行函式的引數。
返回值:若成功則返回0,否則返回出錯編號。
// 等待一個執行緒的結束
int pthread_join(pthread_t thread, void **retval);
引數:
第一個引數為被等待的執行緒識別符號,第二個引數為一個使用者定義的指標,它可以用來儲存被等待執行緒的返回值。這個函式是一個執行緒阻塞的函式,呼叫它的函式將一直阻塞到被等待的執行緒結束為止,當函式返回時,被等待執行緒的資源被收回。
返回值:如果執行成功,將返回0,如果失敗則返回一個錯誤號。
4.執行緒物件的生命週期和執行緒的生命週期是不一樣的。執行緒物件的銷燬一般是程式結束的時候,設定autoDelete_標誌,使執行緒執行完畢,執行緒物件自動銷燬。(需動態建立物件)
從上述過程中我們可以看出:物件導向的程式設計風格主要還是圍繞物件導向的三大特點“封裝、繼承、多型”來展開的——通過封裝隱藏實現細節,只暴露需要對外提供的屬性或方法;通過繼承的特性來提高程式的可複用性;定義抽象基類(純虛擬函式),讓其派生類繼承並實現它的介面方法。
基於物件的程式設計風格
“物件導向”和“基於物件”都實現了“封裝”的概念,但是物件導向實現了“繼承和多型”,而“基於物件”是使用物件,無法利用現有的物件模板產生新的物件型別繼而產生新的物件,沒有實現“繼承和多型”。
基於物件的程式設計風格要藉助boost bind/function來實現,用它來代替虛擬函式作為類的(回撥)介面。它的出現替代了STL中的mem_fun、ptr_fun、bind1st、bin2nd等函式,其主要作用是使一種型別的函式介面轉換成另一種型別的函式介面,且boost bind/function的通用性更好。
首先來了解一下boost bind/function,一個簡單的使用示例如下:
#include <iostream>
// 需包含對應的標頭檔案
#include <boost/function.hpp>
#include <boost/bind.hpp>
using namespace std;
class Foo
{
public:
// 類的非靜態成員函式
void memberFunc(double d, int i, int j)
{
cout << d << endl;
cout << i << endl;
cout << j << endl;
}
};
int main()
{
Foo foo;
// boost::function + boost::bind 把一個類成員函式適配成為一個返回值型別為void,接收一個int型別引數的函式:void(int)
// 對於非靜態成員函式,bind的實參表依次為: 指向成員函式的指標, 繫結到this的物件, n個引數
// 如果我們不打算向成員函式繫結第2個引數,這裡使用 _1 來佔位,同理下邊還會用到 _2 _3 這樣的佔位符.
boost::function<void (int)> fp = boost::bind(&Foo::memberFunc, &foo, 0.5, _1, 10);
fp(100); // 呼叫foo::memberFunc(0.5, 100, 10),列印0.5,100,10
// 這裡我們用到了boost::ref,bind庫可以搭配ref庫使用,ref庫包裝了物件的引用,可以讓bind儲存物件引用的拷貝,從而降低了拷貝的代價
boost::function<void (int, int)> fp2 = boost::bind(&Foo::memberFunc, boost::ref(foo), 0.5, _1, _2);
fp2(100, 200); // 呼叫foo::memberFunc(0.5, 100, 200),列印0.5,100,200
return 0;
}
下面進入正題——使用基於物件的程式設計風格來封裝一個執行緒類,藉助boost::function + boost::bind,我們可以這麼做:令Thread是一個具體類,其建構函式接受ThreadFunc物件,應用程式只需提供一個能轉換為ThreadFunc的物件(可以是函式),即可建立一份Thread實體,然後呼叫Thread::start()即可。
類圖如下:
程式碼如下:
檔名:Thread.h
#ifndef _THREAD_H_
#define _THREAD_H_
#include <pthread.h>
#include <boost/function.hpp>
class Thread
{
public:
typedef boost::function<void ()> ThreadFunc; // 定義執行緒執行函式的型別
explicit Thread(const ThreadFunc& func); // explicit建構函式用來防止隱式轉換
void Start();
void Join();
void SetAutoDelete(bool autoDelete);
private:
static void* ThreadRoutine(void* arg);
void Run();
ThreadFunc func_;
pthread_t threadId_;
bool autoDelete_;
};
#endif // _THREAD_H_
檔名:Thread.cpp
#include "Thread.h"
#include <iostream>
using namespace std;
// 建構函式
Thread::Thread(const ThreadFunc& func) : func_(func), autoDelete_(false)
{
}
// 建立執行緒
void Thread::Start()
{
pthread_create(&threadId_, NULL, ThreadRoutine, this);
}
// 等待執行緒結束
void Thread::Join()
{
pthread_join(threadId_, NULL);
}
// 執行緒入口函式
void* Thread::ThreadRoutine(void* arg)
{
Thread* thread = static_cast<Thread*>(arg);
thread->Run();
if (thread->autoDelete_)
delete thread;
return NULL;
}
// 設定自動銷燬標識
void Thread::SetAutoDelete(bool autoDelete)
{
autoDelete_ = autoDelete;
}
// Run方法直接呼叫func_()
void Thread::Run()
{
func_();
}
檔名:Thread_test.cpp
#include "Thread.h"
#include <boost/bind.hpp>
#include <unistd.h>
#include <iostream>
using namespace std;
class Foo
{
public:
Foo(int count) : count_(count)
{
}
void MemberFun()
{
while (count_--)
{
cout<<"this is a test ..."<<endl;
sleep(1);
}
}
void MemberFun2(int x)
{
while (count_--)
{
cout<<"x="<<x<<" this is a test2 ..."<<endl;
sleep(1);
}
}
int count_;
};
void ThreadFunc()
{
cout<<"ThreadFunc ..."<<endl;
}
void ThreadFunc2(int count)
{
while (count--)
{
cout<<"ThreadFunc2 ..."<<endl;
sleep(1);
}
}
int main()
{
// 呼叫普通函式
Thread t1(ThreadFunc);
Thread t2(boost::bind(ThreadFunc2, 3));
// 呼叫成員函式
Foo foo(3);
Thread t3(boost::bind(&Foo::MemberFun, &foo));
Foo foo2(3);
Thread t4(boost::bind(&Foo::MemberFun2, &foo2, 1000));
t1.Start();
t2.Start();
t3.Start();
t4.Start();
t1.Join();
t2.Join();
t3.Join();
t4.Join();
return 0;
}
可見,基於物件的程式設計風格不暴露抽象類,只使用具體類。
小結
重新審視“物件導向”:“繼承與多型”的特性為程式開發帶來了靈活性,同時,我們也要注意到“虛擬函式作為介面的弊端——一旦釋出,不能修改”、“基於繼承的設計會帶來強耦合性”等問題,所以,究竟是採用“物件導向”還是“基於物件”的設計值得我們在開發時深入思考。
相關文章
- 物件導向程式設計風格 VS 基於物件程式設計風格(boost::bind/function)物件程式設計Function
- 程式設計基礎·Java學習筆記·物件導向(下)程式設計Java筆記物件
- [.net 物件導向程式設計基礎] (2) 關於物件導向程式設計物件程式設計
- [筆記]物件導向的程式設計筆記物件程式設計
- 物件導向與函式程式設計的比較物件函式程式設計
- Javascript程式設計風格JavaScript程式設計
- 糟糕程式設計師的程式設計風格程式設計師
- Python程式設計風格和設計模式Python程式設計設計模式
- javascript: 基於原型的物件導向程式設計JavaScript原型物件程式設計
- JavaScript 程式設計風格指南JavaScript程式設計
- Java SE 學習---物件導向程式設計Java物件程式設計
- C#學習筆記(六)——物件導向程式設計簡介C#筆記物件程式設計
- Python3:物件導向程式設計學習筆記(2)Python物件程式設計筆記
- 學習筆記:物件導向程式設計技術(C++版)筆記物件程式設計C++
- 物件導向程式設計物件程式設計
- 物件導向:說說程式設計師不解風情的瞬間物件程式設計師
- 關於程式設計風格的討論 (轉)程式設計
- 優秀Java程式設計師的程式設計風格Java程式設計師
- 【閱讀筆記】REST設計風格筆記REST
- Python學習之物件導向程式設計Python物件程式設計
- Google Java 程式設計風格指南GoJava程式設計
- 物件導向程式設計和`GP`泛型程式設計物件程式設計泛型
- 各種流行的程式設計風格程式設計
- 如何使用Go語言寫出物件導向風格的程式碼Go物件
- JS物件導向程式設計(一):物件JS物件程式設計
- 函數語言程式設計 vs 物件導向程式設計 vs 程式式程式設計的JS演示比較 - DEV函數程式設計物件JSdev
- 十三、物件導向程式設計物件程式設計
- js物件導向程式設計JS物件程式設計
- 程式設計思想 物件導向程式設計物件
- 十六、物件導向程式設計物件程式設計
- perl 物件導向程式設計物件程式設計
- LotusScript物件導向程式設計物件程式設計
- Javascript 物件導向程式設計JavaScript物件程式設計
- Scala的物件導向程式設計物件程式設計
- JS物件導向的程式設計JS物件程式設計
- javascript:物件導向的程式設計JavaScript物件程式設計
- [.net 物件導向程式設計深入](0) 開篇物件程式設計
- Python學習之路——類-物件導向程式設計Python物件程式設計