面向对象编程风格 & 基于对象编程(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类图

面向对象编程风格 & 基于对象编程(boost::bind/function)_第1张图片

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); //没有隐含的this 指针
    virtual void Run() = 0;
    pthread_t threadId_;
    bool autoDelete_;
};

#endif // _THREAD_H_
Tread.cpp

#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:
#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) 基类的析构函数不用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 <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 (int, int)> fp2 = boost::bind(&Foo::memberFunc, &foo, 0.5, _1, _2);
    fp2(100, 200);
    boost::function<void (int, int)> 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);

面向对象编程风格 & 基于对象编程(boost::bind/function)_第2张图片

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

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

    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 <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:
#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是一个网络库,如何使用它呢?那要看它是如何实现的:
(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_;
};


你可能感兴趣的:(thread,面向对象编程,boost,基于对象编程)