muduo网络库学习笔记(0):面向对象编程风格和基于对象编程风格的比较

我们通过对一个线程类的封装来比较这二者的不同。

面向对象编程风格

使用面向对象编程风格封装线程类,一般这么做:写一个Thread base class,含有(纯)虚函数Thread::run(),然后应用程序派生一个derived class,覆写run()。程序里的每一种线程对应一个Thread的派生类。

类图如下:
muduo网络库学习笔记(0):面向对象编程风格和基于对象编程风格的比较_第1张图片

代码如下:

文件名: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);  // 线程的入口函数
    virtual void Run() = 0;  // 真正的线程执行体,声明为纯虚函数,作为一个接口,不同的派生类有不同的实现
    pthread_t threadId_;  // 线程ID
    bool autoDelete_;  // 自动销毁标识
};

#endif // _THREAD_H_
文件名:Thread.cpp -- 源文件

#include "Thread.h"
#include 
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(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 
#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.Thread类作为抽象类,是不能实例化对象的。TestThread类是派生类,是具体类。

2.因为Thread类作为多态基类,该类的析构函数必须是一个纯虚析构函数,否则会导致派生类的资源释放不完全。

3.线程的相关函数:
包含头文件:#include

// 创建线程
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 

// 需包含对应的头文件
#include 
#include 

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()即可。

类图如下:
muduo网络库学习笔记(0):面向对象编程风格和基于对象编程风格的比较_第2张图片

代码如下:

文件名:Thread.h

#ifndef _THREAD_H_
#define _THREAD_H_

#include 
#include 

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 
#include 
#include 
using namespace std;

class Foo
{
public:
    Foo(int count) : count_(count)
    {
    }

    void MemberFun()
    {
        while (count_--)
        {
            cout<<"this is a test ..."<1);
        }
    }

    void MemberFun2(int x)
    {
        while (count_--)
        {
            cout<<"x="<" this is a test2 ..."<1);
        }
    }

    int count_;
};

void ThreadFunc()
{
    cout<<"ThreadFunc ..."<void ThreadFunc2(int count)
{
    while (count--)
    {
        cout<<"ThreadFunc2 ..."<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;
}

可见,基于对象的编程风格不暴露抽象类,只使用具体类。

小结

重新审视“面向对象”:“继承与多态”的特性为程序开发带来了灵活性,同时,我们也要注意到“虚函数作为接口的弊端——一旦发布,不能修改”、“基于继承的设计会带来强耦合性”等问题,所以,究竟是采用“面向对象”还是“基于对象”的设计值得我们在开发时深入思考。

你可能感兴趣的:(C/C++,muduo)