面向对象设计原则
接口隔离原则:面向对象设计之接口隔离原则-CSDN博客
设计模式
工厂模式 : 设计模式之工厂模式-CSDN博客
迭代器模式:设计模式之迭代器模式-CSDN博客
适配器模式:设计模式之适配器模式-CSDN博客
过滤器模式:设计模式之过滤器模式-CSDN博客
观察者模式:设计模式之观察者模式-CSDN博客
空对象模式:设计模式之空对象模式-CSDN博客
责任链模式:设计模式之责任链模式-CSDN博客
策略模式:设计模式之策略模式-CSDN博客
Pimpl技法:C++之Pimpl惯用法-CSDN博客
桥接模式: 设计模式之桥接模式-CSDN博客
组合模式:设计模式之组合模式-CSDN博客
单例模式:设计模式之单例模式-CSDN博客
目录
1.简介
2.案例分析
3.用法
4.优点
Pimpl即“pointer to implementation”(指向实现的指针)。该技巧可以避免在头文件中暴露私有细节,是促进API接口和实现保持完全分离的重要机制, 从而减少编译依赖和提高编译速度。
如下图为impl常见的内存布局,class A只提供公有接口func1, func2,其实现细节由Impl类实现,class A通过一格Impl 指针impl来提供服务。这样做的目的在于,使用class A公有接口的用户,不必关系其实现细节,而且实现的变动,对用户也是透明的。
Impl类成员函数privateFunc1、privateFunc2、privateFunc3和成员变量privateData1、privateData2都声明为private, 在头部声明friend class A既保证了class A的顺利访问Impl的所有接口和私有数据,又对外隐藏了所有的接口和私有数据,起到了很好的保护作用。
在Qt中最基础的类就是QObject, QObject是Qt对象模型的核心,所以QObject类的设计至关重要。下面看一下在Qt5.12.12源码中QObject的设计,以QWidget为例介绍,我整理了几个类之间的关系,类图如下:
QObject如果说是上图的class A,那么QObjectData就是Impl。QObject和QObjectData之间的纽带就是d指针,QObjectData和QObject之间的纽带是q指针,d指针和q指针的定义如下:
template inline T *qGetPtrHelper(T *ptr) { return ptr; }
template inline auto qGetPtrHelper(const Ptr &ptr) -> decltype(ptr.operator->()) { return ptr.operator->(); }
// The body must be a statement:
#define Q_CAST_IGNORE_ALIGN(body) QT_WARNING_PUSH QT_WARNING_DISABLE_GCC("-Wcast-align") body QT_WARNING_POP
#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() \
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast(qGetPtrHelper(d_ptr));) } \
inline const Class##Private* d_func() const \
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast(qGetPtrHelper(d_ptr));) } \
friend class Class##Private;
#define Q_DECLARE_PRIVATE_D(Dptr, Class) \
inline Class##Private* d_func() \
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast(qGetPtrHelper(Dptr));) } \
inline const Class##Private* d_func() const \
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast(qGetPtrHelper(Dptr));) } \
friend class Class##Private;
#define Q_DECLARE_PUBLIC(Class) \
inline Class* q_func() { return static_cast(q_ptr); } \
inline const Class* q_func() const { return static_cast(q_ptr); } \
friend class Class;
#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()
Q_DECLARE_PRIVATE 在 QObject 类中使用了,下面的代码可以看出:
qobject.h
class Q_CORE_EXPORT QObject
{
Q_OBJECT
Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
Q_DECLARE_PRIVATE(QObject)
public:
...
protected:
QObject(QObjectPrivate &dd, QObject *parent = nullptr);
protected:
QScopedPointer d_ptr;
...
};
在QObject中一般直接使用d_ptr指针,但是在它的派生类QWidget中一般使用Q_D宏来访问QWidgetPrivate里面的数据,从QWidget的成员函数里面随处可以看使用Q_D的,比如:
QSizePolicy QWidget::sizePolicy() const
{
Q_D(const QWidget);
return d->size_policy;
}
QObjectData的定义如下:
class Q_CORE_EXPORT QObjectData {
public:
virtual ~QObjectData() = 0;
QObject *q_ptr;
QObject *parent;
QObjectList children;
uint isWidget : 1;
uint blockSig : 1;
uint wasDeleted : 1;
uint isDeletingChildren : 1;
uint sendChildEvents : 1;
uint receiveChildEvents : 1;
uint isWindow : 1; //for QWindow
uint deleteLaterCalled : 1;
uint unused : 24;
int postedEvents;
QDynamicMetaObjectData *metaObject;
QMetaObject *dynamicMetaObject() const;
};
QWidgetPrivate使用Q_DECLARE_PUBLIC,从下面的代码可以看出:
class Q_WIDGETS_EXPORT QWidgetPrivate : public QObjectPrivate
{
Q_DECLARE_PUBLIC(QWidget)
...
};
在QObjectPrivate中一般直接使用q_ptr指针,但是在它的派生类QWidgetPrivate中一般使用Q_Q宏来访QWidget里面的成员函数,从QWidgetPrivate的成员函数里面随处可以看使用Q_Q的,比如:
void QWidgetPrivate::create()
{
Q_Q(QWidget);
...
}
总结,通过上面的分析,d指针和q指针的关系很清晰了;Qt里面的类体系设计都是Pimpl惯用法的践行者。
使用裸指针指向Impl,既不安全,又怕程序退出时候忘记delete它,导致内存泄漏;所有此处可以借助智能指针(smart pointer)解决该问题,具体来说,可采用std::shared_ptr(共享指针),或std::unique_ptr(域指针)指向Impl类对象。
下面我工作过程中写一个部分具体实现:
CommunicationDataTask.h
class CCommunicationDataTask : public CLinkTaskAdapter, public IThread
{
public:
explicit CCommunicationDataTask(IWaveProtocolBasePacketByS* pProtocol,
CSocketBase* pComm,
const QString& dataFile,
int channel,
quint32 signalType,
quint64 packetSizeOnce = 32*1024*1024, //每包发送大小, 默认32M
ILinkDataSimpleProgressNotify* pNotify = nullptr);
virtual ~CCommunicationDataTask();
public:
int start() override;
int stop() override;
int getType() const override {return eTransFileTask;}
int getState() const override;
void update(const void* param = nullptr) override;
...
private:
class CCommunicationDataTaskImpl;
std::unique_ptr d_ptr;
};
CommunicationDataTask.cpp
class CCommunicationDataTaskImpl
{
friend class CCommunicationDataTask;
public:
explicit CCommunicationDataTaskImpl(IWaveProtocolBasePacketByS* pProtocol,
CSocketBase* pComm,
const QString& dataFile,
int channel,
quint32 signalType,
quint64 packetSizeOnce,
ILinkDataSimpleProgressNotify* pNotify)
: m_pProtocol(pProtocol)
, m_pSocket(pComm)
, m_file(dataFile)
, m_channel(channel)
, m_signalType(signalType)
, m_packetSizeOnce(packetSizeOnce)
, m_pNotify(pNotify)
, m_states(eIdle)
, m_sendedPos(0)
, m_bWait(false)
, m_bStop(false)
{
}
private:
IWaveProtocolBasePacketByS* m_pProtocol;
CSocketBase* m_pSocket;
QString m_file;
const int m_channel;
const quint32 m_signalType;
ILinkDataSimpleProgressNotify* m_pNotify;
int m_states;
QByteArray m_data;
int m_sendedPos;
std::atomic m_bWait;
bool m_bStop;
const quint64 m_packetSizeOnce;
};
CCommunicationDataTask::CCommunicationDataTask(IWaveProtocolBasePacketByS* pProtocol,
CSocketBase* pComm,
const QString& dataFile,
int channel,
quint32 signalType,
quint64 packetSizeOnce,
ILinkDataSimpleProgressNotify* pNotify)
: d_ptr(new CCommunicationDataTaskImpl(pProtocol, pComm,dataFile, channel, signalType, packetSizeOnce, pNotify))
{
}
CCommunicationDataTask::~CCommunicationDataTask()
{
stop();
}
int CCommunicationDataTask::start()
{
if (d_ptr->m_states == eRunning){
return 1;
}
IThread::start();
d_ptr->m_states = eRunning;
return 1;
}
int CCommunicationDataTask::stop()
{
if (d_ptr->m_states == eStopped ||
d_ptr->m_states == eFinished){
return 1;
}
qDebug() << "CCommunicationDataTask::stop, run into, time: " << QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
d_ptr->m_bStop = true;
join();
d_ptr->m_states = eStopped;
qDebug() << "CCommunicationDataTask::stop, after, time:" << QDateTime::currentDateTime().toString("hh:mm:ss.zzz");
return 1;
}
int CCommunicationDataTask::getState() const
{
return d_ptr->m_states;
}
void CCommunicationDataTask::update(const void* param)
{
assert(param != nullptr);
bool bEnable = *static_cast(param);
d_ptr->m_bWait.store(bEnable);
}
...
这里的d_ptr类似Qt中的d指针,通过仅存储单个指针来使库的所有公共类的大小保持恒定。该指针指向包含所有数据的私有/内部数据结构。内部结构的大小可以缩小或增长,而对应用程序没有任何副作用,因为仅在库代码中访问指针,并且从应用程序的角度来看,对象的大小从不改变-始终是对象的大小。