物件導向程式設計風格 VS 基於物件程式設計風格(boost::bind/function)

s1mba發表於2013-10-22

本文主要通過實現Thread 類來展現兩種程式設計風格的不同點。


很多人沒有區分“物件導向”和“基於物件”兩個不同的概念。物件導向的三大特點(封裝,繼承,多型)缺一不可。通常“基於物件”是使用物件,但是無法利用現有的物件模板產生新的物件型別,繼而產生新的物件,也就是說“基於物件”沒有繼承的特點。而“多型”表示為父類型別的子類物件例項,沒有了繼承的概念也就無從談論“多型”。現在的很多流行技術都是基於物件的,它們使用一些封裝好的物件,呼叫物件的方法,設定物件的屬性。但是它們無法讓程式設計師派生新物件型別。他們只能使用現有物件的方法和屬性。所以當你判斷一個新的技術是否是物件導向的時候,通常可以使用後兩個特性來加以判斷。“物件導向”和“基於物件”都實現了“封裝”的概念,但是物件導向實現了“繼承和多型”,而“基於物件”沒有實現這些。----摘自網路


一、物件導向程式設計風格

Thread 類圖:

注:下劃線表示靜態成員

Thread.h:
 C++ Code 
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
 
#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); //沒有隱含的this 指標
    virtual void Run() = 0;
    pthread_t threadId_;
    bool autoDelete_;
};

#endif // _THREAD_H_

Thread.cpp:
 C++ Code 
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
 
#include "Thread.h"
#include <iostream>
using namespace std;


Thread::Thread() : autoDelete_(false)
{
    cout << "Thread ..." << endl;
}

Thread::~Thread()
{
    cout << "~Thread ..." << endl;
}

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;
}

Thread_test.cpp:
 C++ Code 
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
41
42
43
44
 
#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類繼承來實現虛擬函式run()。
2、根據 pthread_create 的原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

start_routine 引數是一般的函式指標,故不能直接將run() 作為此引數,因為run()是成員函式,隱含this指標,故實現一個靜態成員函式ThreadRoutine(), 在裡面呼叫run(),此外引數arg 我們傳遞this指標,在ThreadRoutine()內將派生類指標轉換為基類指標來呼叫run()。

3、把run()實現為private是為了不讓使用者直接呼叫,因為這樣根本就沒有產生執行緒排程。
4、注意區分執行緒與執行緒物件,設定autoDetele_ 成員也是為了當執行緒結束時能夠立刻銷燬執行緒物件。在main函式內,主執行緒pthread_join()等待執行緒結束;run()結束後會delete 掉執行緒物件,否則要一直等到main函式結束才會被自動銷燬。


二、基於物件程式設計風格

boost bind/function庫的出現,替代了stl中的mem_fun,ptr_fun  ,bind1st,bin2nd等函式,這些函式參考這裡
下面舉例boost bind/function 的使用。

 C++ Code 
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
 
#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;//列印0.5
        cout << i << endl;//列印100
        cout << j << endl;//列印10
    }
};
int main()
{
    Foo foo;
    boost::function<void (int)> fp = boost::bind(&Foo::memberFunc, &foo, 0.5, _1, 10);
    fp(100);
    boost::function<void (intint)> fp2 = boost::bind(&Foo::memberFunc, &foo, 0.5, _1, _2);
    fp2(100200);
    boost::function<void (intint)> fp3 = boost::bind(&Foo::memberFunc, boost::ref(foo), 0.5, _1, _2);
    fp3(5566);
    return 0;
}


boost bind/function 實現轉換函式介面。
fp(100); 等價於 (&foo)->memberFunc(0.5, 100, 10); 即_1 是佔位符,如果繫結的是一般的函式,則bind 中的引數中不再需要this指標,當然一般函式也沒有類名字首。
boost::ref() 表示引用,fp3(55, 66); 相當於foo.memberFunc(0.5, 55, 66);

Thread 類圖:

typedef boost::function<void ()> ThreadFunc;


Thread.h:
 C++ Code 
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
 
#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);

    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:
 C++ Code 
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
 
#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;
}

void Thread::Run()
{
    func_();
}

Thread_test.cpp:
 C++ Code 
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
 
#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(void)
{
    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;
}


注意:Thread類不再是虛基類,run() 也不是虛擬函式,Thread 有個成員ThreadFunc func_,此時不再是通過繼承基類來重新實現run(),進而實現多型;而是通過繫結不同的函式指標到func_ 上來實現不同的行為。我們既可以繫結一般的全域性函式,也可以繫結其他類裡面的成員函式,操作很方便。此外,Thread t3, t4 不能繫結到同一個類物件foo 上,因為此時MemFun() 和MemFun2() 都會去訪問同一個物件foo的count_ ,就會出現問題了。


假設TcpServer是一個網路庫,如何使用它呢?那要看它是如何實現的:

C程式設計風格:註冊三個全域性函式到網路庫,網路庫函式的引數有函式指標型別,裡面通過函式指標來回撥。

物件導向風格:用一個EchoServer繼承自TcpServer(抽象類),實現三個純虛擬函式介面OnConnection, OnMessage, OnClose。通過基類指標呼叫虛擬函式實現多型。

基於物件風格:用一個EchoServer包含一個TcpServer(具體類)物件成員server,在建構函式中用boost::bind 來註冊三個成員函式,如server.SetConnectionCallback(boost::bind(&EchoServer::OnConnection, ...)); 也就是設定了server.ConnectionCallback_ 成員,通過繫結不同的函式指標,呼叫server.ConnectionCallback_() 時就實現了行為的不同。如下所示。

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class EchoServer
{
public:
    EchoServer()
    {
        server_.SetConnectionCallback(boost::bind(&EchoServer::OnConnection, ...));
                                      ...
    }
    void OnConnection()
    {
        ..
    }

    TcpServer server_;
};


參考:
muduo manual.pdf
《linux 多執行緒伺服器程式設計:使用muduo c++網路庫》

相關文章