C++ Pimpl编程技法

本文介绍了C++中的Pimpl(指向实现的指针)技巧,这是一种用于解耦类接口与实现的方法,能够有效地降低耦合度、实现信息隐藏、减少编译依赖并提高编译速度。通过具体的Book类实例,展示了如何使用Pimpl实现接口与实现的分离。

代码参考:github

昨天在看EffectiveC++,提到一个C++编程技巧,Pimpl(pointer to implementation)。

简单的理解就是在公共接口里面封装私有数据与方法,class提供更好的封装,下面来看看具体实现。

Pimpl(pointer to implementation, 指向实现的指针)是一种常用的,用来对“类的接口与实现”进行解耦的方法。这个技巧可以避免在头文件中暴露私有细节(见下图1),因此是促进API接口与实现保持完全分离的重要机制。但是Pimpl并不是严格意义上的设计模式(它是受制于C++特定限制的变通方案),这种惯用法可以看作桥接设计模式的一种特例。

在类中使用Pimpl惯用法,具有如下优点:

降低耦合
信息隐藏
降低编译依赖,提高编译速度
接口与实现分离

比如说,假设我们有Book class,现在为他添加一个实现细节,并封装到BookImpl类中:

// Book.h

#ifndef __BOOK_H__
#define __BOOK_H__
class Book
{
    public:
        Book();
        ~Book();
        void print();

    private:
        class BookImpl;
        BookImpl* pimpl;
};
#endif

为了实现接口与实现分离,这里的私有成员类对Book中的接口完成进一步封装:

//BookImpl.h

#ifndef __BOOKIMPL_H__
#define __BOOKIMPL_H__
#include "Book.h"
#include <iostream>
#include <string>
class Book::BookImpl
{
    public:
        void print();
    private:
        std::string content_;
        std::string titil_;
};
#endif

最后来看看实现,所有的共有接口都由BookImpl来实现:

//Book.cpp

#include "Book.h"
#include "BookImpl.h"

Book::Book()
{
    pimpl = new BookImpl();
}
Book::~Book()
{
    delete pimpl;
}
void Book::print()
{
    pimpl->print();
}
void Book::BookImpl::print()
{
    std::cout<<"print in imple"<<std::endl;
}

用户在使用的时候并不知道内部的构造,调用的时候就正常调用即可:

//main.cpp

#include "Book.h"
int main()
{
    Book book;
    book.print();

    return 0;
}

由于Pimpl解除了接口与实现之间的耦合关系,从而降低文件间的编译依赖关系,Pimpl也因此常被称为“编译期防火墙“ 。

顺便贴一下cppreference中的代码:

#include <iostream>
#include <memory>
#include <experimental/propagate_const>

// interface (widget.h)
class widget {
    class impl;
    std::experimental::propagate_const<std::unique_ptr<impl>> pImpl;
 public:
    void draw() const; // public API that will be forwarded to the implementation
    void draw();
    bool shown() const { return true; } // public API that implementation has to call
    widget(int);
    ~widget(); // defined in the implementation file, where impl is a complete type
    widget(widget&&) = default;  // Note: calling draw() on moved-from object is UB
    widget(const widget&) = delete;
    widget& operator=(widget&&); // defined in the implementation file
    widget& operator=(const widget&) = delete;
};

// implementation (widget.cpp)
class widget::impl {
    int n; // private data
 public:
    void draw(const widget& w) const {
        if(w.shown()) // this call to public member function requires the back-reference 
            std::cout << "drawing a const widget " << n << '\n';
    }
    void draw(const widget& w) {
        if(w.shown())
            std::cout << "drawing a non-const widget " << n << '\n';
    }
    impl(int n) : n(n) {}
};
void widget::draw() const { pImpl->draw(*this); }
void widget::draw() { pImpl->draw(*this); }
widget::widget(int n) : pImpl{std::make_unique<impl>(n)} {}
widget::~widget() = default;
widget& widget::operator=(widget&&) = default;

// user (main.cpp)
int main()
{
    widget w(7);
    const widget w2(8);
    w.draw();
    w2.draw();
}

参考:
http://blog.youkuaiyun.com/lihao21/article/details/47610309
http://en.cppreference.com/w/cpp/language/pimpl

### C++PIMPL 模式的实现与用途 PIMPL(Pointer to IMPLementation)是一种设计模式,用于隐藏类的实现细节并减少编译依赖。通过将类的具体实现封装到一个独立的对象中并通过指针访问该对象,可以在头文件中仅暴露必要的接口定义。 #### 实现方式 以下是基于 `std::unique_ptr` 的典型 PIMPL 模式实现: ```cpp // Header file: Person.h #ifndef PERSON_H #define PERSON_H #include <memory> #include <string> class Person { public: explicit Person(const std::string& name); ~Person(); void setName(const std::string& name); const std::string& getName() const; private: class Impl; std::unique_ptr<Impl> impl_; }; #endif // PERSON_H // Source file: Person.cpp #include "Person.h" #include <iostream> class Person::Impl { public: Impl(const std::string& name) : name_(name) {} ~Impl() {} void setName(const std::string& name) { name_ = name; } const std::string& getName() const { return name_; } private: std::string name_; }; Person::Person(const std::string& name) : impl_(std::make_unique<Impl>(name)) {} Person::~Person() {} void Person::setName(const std::string& name) { impl_->setName(name); } const std::string& Person::getName() const { return impl_->getName(); } ``` 在此示例中,`Person` 类的私有成员是一个指向未公开内部实现类 `Impl` 的 `std::unique_ptr`。这种分离使得用户无需知道 `Person` 的具体实现细节[^1]。 #### 使用场景 PIMPL 模式通常适用于以下情况: - 需要降低二进制兼容性风险。 - 希望减少头文件中的依赖项以加快构建时间。 - 提供稳定的 ABI 接口而不影响底层实现的变化。 尽管如此,在现代 C++ 编程实践中需要注意性能开销以及额外内存分配的影响[^2]。 #### 设计注意事项 为了保持接口稳定性同时允许子类自定义行为,建议采用模板方法模式而非直接开放虚拟函数给派生类重写[^3]。这有助于维护一致性的外部 API 定义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值