我们通过对一个线程类的封装来比较这二者的不同。
使用面向对象编程风格封装线程类,一般这么做:写一个Thread base class,含有(纯)虚函数Thread::run(),然后应用程序派生一个derived class,覆写run()。程序里的每一种线程对应一个Thread的派生类。
代码如下:
文件名: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()即可。
代码如下:
文件名: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;
}
可见,基于对象的编程风格不暴露抽象类,只使用具体类。
重新审视“面向对象”:“继承与多态”的特性为程序开发带来了灵活性,同时,我们也要注意到“虚函数作为接口的弊端——一旦发布,不能修改”、“基于继承的设计会带来强耦合性”等问题,所以,究竟是采用“面向对象”还是“基于对象”的设计值得我们在开发时深入思考。