程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 面向對象編程風格 & 基於對象編程(boost::bind/function)

面向對象編程風格 & 基於對象編程(boost::bind/function)

編輯:C++入門知識

面向對象編程風格 & 基於對象編程(boost::bind/function)


“Muduo是一個現代的 C++ 網絡庫。現代和古代的API區別在於兩方面。一個是事件回調,另外一個是資源管理。一般的網絡庫設計API的方式是定義一個接口(抽象基類),包含幾種網絡事件對應的處理函數。你的代碼去繼承這個接口,這個接口會定義收到消息是回調哪個虛函數,然後你覆蓋一下這個虛函數。然後把你的對象注冊到網絡庫中,發生事件的時候就回調你的虛函數。一般的 Framework 都這麼搞,這就是傳統的或者說古代的 C++ 網絡庫的做法,也是Java網絡庫的做法。這種做法在C++中面臨的一個直接問題是對象的生命期管理,因為C++的動態綁定只能通過指針和引用來實現,你必須把基類指針傳給framework,才能獲得事件回調。那麼這個派生類對象何時銷毀就成了難點,它的所有權到底歸誰?有的網絡庫甚至在事件處理函數中出現了delete this;這種代碼,讓人捏一把汗。

我現在的回調方式是用boost::function,它在TR1時已經進入 C++ 標准庫。Boost::function不對類型和函數名做限制,只對參數和返回類型做部分限制。如果你通過傳統的繼承來回調的話,你這個類型必須是framework裡某個基類的派生類,函數的名字必須一樣,參數列表必須一樣,返回類型也基本肯定是一樣。但是boost::function沒有這些限制。Muduo網絡庫不是一個面向對象(object-oriented)的庫,它是一個基於對象(object-based)的庫。它在接口上沒有表現出繼承的特性,它用的是boost function的注冊/回調機制,網絡事件的表示就用 boost function。所以對Muduo來講,它不需要知道你寫什麼類,也不強迫繼承,更不需要知道你的函數叫什麼名字,你給它的就是一個 boost function對象,限制就很少。而且你沒有把對象指針傳給網絡庫,那麼就可以按原有的方式管理對象的生命期。”

--------------陳碩,開源社區訪談實錄

面向對象的三大特點(封裝,繼承,多態)缺一不可。通常“基於對象”是使用對象,但是無法利用現有的對象模板產生新的對象類型,繼而產生新的對象,也就是說“基於對象”沒有繼承的特點。而“多態”表示為父類類型的子類對象實例,沒有了繼承的概念也就無從談論“多態”。現在的很多流行技術都是基於對象的,它們使用一些封裝好的對象,調用對象的方法,設置對象的屬性。但是它們無法讓程序員派生新對象類型。他們只能使用現有對象的方法和屬性。所以當你判斷一個新的技術是否是面向對象的時候,通常可以使用後兩個特性來加以判斷。“面向對象”和“基於對象”都實現了“封裝”的概念,但是面向對象實現了“繼承和多態”,而“基於對象”沒有實現這些。

本文通過實現Tread類來比較兩種編程風格的差別。

(一)面向對象編程風格

Tread類圖

\

Thread.h

 

#ifndef _THREAD_H_
#define _THREAD_H_

#include 

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_
Tread.cpp

 

 

#include "Thread.h"
#include 
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(arg);
    thread->Run(); //線程結束,線程對象也得析構
    if (thread->autoDelete_)
        delete thread;
    return NULL;
}

void Thread::SetAutoDelete(bool autoDelete)
{
    autoDelete_ = autoDelete;
}
Thread_test.cpp:
#include "Thread.h"
#include 
#include 
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) 基類的析構函數不用virtual會發生什麼?

可能會產生內存洩露。如果在派生類中申請了內存空間,並在其析構函數中釋放,如果此時基類的析構函數不是virtual,就不會觸發動態綁定,因而只會調用基類的析構函數,從而導致派生類對象的一些內存空間沒有被釋放,導致內存洩露。

(2) Thread類中 virtual void Run()=0 是純虛函數,含有純虛函數的類是抽象類,不能生成對象,所以要繼承此基類並實現Run();

(3) 線程結束和線程對象的銷毀是不同的概念,線程對象的生命周期要等到作用域結束。如果想要實現線程執行完畢,線程對象自動銷毀,就需要動態創建對象,使用delete在run之後就將線程對象delete銷毀掉。

(4)根據 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()。

(5)把run()實現為private是為了不讓用戶直接調用,因為這樣根本就沒有產生線程調度。
(二)基於對象編程風格

函數適配器 boost bind/function用來實現轉換函數接口。

#include 
#include 
#include 
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 fp = boost::bind(&Foo::memberFunc, &foo, 0.5, _1, 10);
    fp(100);
    boost::function fp2 = boost::bind(&Foo::memberFunc, &foo, 0.5, _1, _2);
    fp2(100, 200);
    boost::function fp3 = boost::bind(&Foo::memberFunc, boost::ref(foo), 0.5, _1, _2);
    fp3(55, 66);
    return 0;
}

fp(100); 等價於 (&foo)->memberFunc(0.5, 100, 10); 即_1 是占位符,如果綁定的是一般的函數,則bind 中的參數中不再需要this指針,當然一般函數也沒有類名前綴。成員函數的取地址符號不可以省略。

boost::ref() 表示引用,fp3(55, 66); 相當於foo.memberFunc(0.5, 55, 66);

\

typedef boost::function ThreadFunc;

Thread.h
#ifndef _THREAD_H_
#define _THREAD_H_

#include 
#include 

class Thread
{
public:
    typedef boost::function 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_
Tread.cpp

 

 

#include "Thread.h"
#include 
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(arg);
    thread->Run();
    if (thread->autoDelete_)
        delete thread;
    return NULL;
}

void Thread::SetAutoDelete(bool autoDelete)
{
    autoDelete_ = autoDelete;
}

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

Thread_test.cpp:
#include "Thread.h"
#include 
#include 
#include 
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是一個網絡庫,如何使用它呢?那要看它是如何實現的: (1) C編程風格:注冊三個全局函數到網絡庫,網絡庫函數的參數有函數指針類型,裡面通過函數指針來回調。 (2)面向對象風格:用一個EchoServer繼承自TcpServer(抽象類),實現三個純虛函數接口OnConnection, OnMessage, OnClose。通過基類指針調用虛函數實現多態。 (3)基於對象風格:用一個EchoServer包含一個TcpServer(具體類)對象成員server,在構造函數中用boost::bind 來注冊三個成員函數,如server.SetConnectionCallback(boost::bind(&EchoServer::OnConnection, ...)); 也就是設置了server.ConnectionCallback_ 成員,通過綁定不同的函數指針,調用server.ConnectionCallback_() 時就實現了行為的不同。如下所示。
class EchoServer
{
public:
    EchoServer()
    {
        server_.SetConnectionCallback(boost::bind(&EchoServer::OnConnection, ...));
                                      ...
    }
    void OnConnection()
    {
        ..
    }

    TcpServer server_;
};

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved