C++接口文件小技巧之PIMPL详解
PIMPL(Pointer to Implementation)
PIMPL模式(指针实现标准库技术)是一种C++的编程技巧,也成为“编译期实现技术”(CTT)。指使用一个指针来指向一个接口类的指针,通过这个指针向实现类的指针,实现对实现类的访问。
PIMPL主要使用技术:
- 前置声明提高编译速度,减少编译时间
- 指针类实现事件管理
PIMPL模式将接口与实现分开,更加灵活,降低了代码依赖性,也更容易维护。
使用PIMPL模式的好处
- 降低头文件依赖,提高编译速度,减少编译时间
- 降低实现类的公开程度,提高代码的安全性
- 只需修改实现类的内容时,不需要重新编译接口
示例1:使用PIMPL模式实现字符串
下面是一个使用PIMPL模式实现字符串的示例,先定义string.h头文件:
// string.h
class StringImpl;
class String{
public:
String();
~String();
int Length();
void Append(char ch);
void Append(const char* str);
private:
StringImpl* pimpl_;
};
然后定义string.cpp文件:
// string.cpp
#include "string.h"
#include <cstring>
class StringImpl{
public:
StringImpl()
: data_(new char[size_]), size_(0), capacity_(default_capacity_){}
~StringImpl(){ delete[] data_;}
int Length(){ return size_;}
void Append(char ch){
EnsureCapacity(1);
data_[size_++] = ch;
}
void Append(const char* str){
auto length = static_cast<int>(strlen(str));
EnsureCapacity(length);
std::memcpy(data_ + size_, str, length);
size_ += length;
}
private:
void EnsureCapacity(int length){
if (size_ + length > capacity_){
capacity_ = std::max(capacity_ * 2, size_ + length);
char* new_data = new char[capacity_];
std::memcpy(new_data, data_, size_);
delete[] data_;
data_ = new_data;
}
}
static const int default_capacity_ = 16;
int size_;
int capacity_;
char* data_;
};
String::String():pimpl_(new StringImpl){}
String::~String(){ delete pimpl_;}
int String::Length(){ return pimpl_->Length();}
void String::Append(char ch){ pimpl_->Append(ch);}
void String::Append(const char* str){ pimpl_->Append(str);}
上面的代码中,String类没有定义析构函数,因为析构函数会自动将StringImpl的析构函数调用。我们只需要在String类的后面定义析构函数,即可将StringImpl的析构函数调用。
示例2:PIMPL模式管理回调函数
下面是一个使用PIMPL模式管理回调函数的示例,我们先定义回调函数的接口:
// callback.h
class CallbackImpl{
public:
virtual ~CallbackImpl(){}
virtual void Run() = 0;
};
class Callback{
public:
Callback();
Callback(const Callback& other);
~Callback();
Callback& operator=(const Callback& other);
void Run();
private:
CallbackImpl* pimpl_;
};
然后定义callback.cpp文件:
//callback.cpp
#include "callback.h"
#include <memory>
class CallbackHolder : public CallbackImpl{
public:
explicit CallbackHolder(std::function<void()> callback)
: callback_(callback){}
void Run() override{
callback_();
}
private:
std::function<void()> callback_;
};
Callback::Callback():pimpl_(nullptr){}
Callback::Callback(const Callback& other){
if (other.pimpl_){
pimpl_ = other.pimpl_;
}
}
Callback::~Callback(){
delete pimpl_;
}
Callback& Callback::operator=(const Callback& other){
if (this == &other) return *this;
delete pimpl_;
pimpl_ = nullptr;
if (other.pimpl_){
pimpl_ = other.pimpl_;
}
return *this;
}
void Callback::Run(){
if (pimpl_){
pimpl_->Run();
}
}
void CreateCallback(std::function<void()> callback, Callback* out_callback){
// 注意这里malloc会申请内存但不会对内存进行初始化,所以需要在使用前对内存进行初始化
void* mem = std::malloc(sizeof(CallbackHolder));
new(mem) CallbackHolder(callback);
out_callback->pimpl_ = static_cast<CallbackImpl*>(mem);
}
上面的代码中,Callback类没有定义构造函数,因为构造函数会自动将CallbackHolder的构造函数调用。我们只需要在Callback类的后面定义构造函数,即可将CallbackHolder的构造函数调用。
总结
PIMPL模式是一种非常实用的C++技术,它将接口与实现进行有效分离,降低了代码依赖性,提高了代码的可维护性和扩展性。但是PIMPL也有一定的缺点,比如会增加代码复杂度,增加调试难度,以及需要动态申请内存等问题。所以在使用PIMPL模式时需要综合考虑各种因素,权衡利弊。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:C++接口文件小技巧之PIMPL详解 - Python技术站