zoukankan      html  css  js  c++  java
  • 实现私有化(Pimpl) --- QT常见的设计模式

    转载自:http://blog.sina.com.cn/s/blog_667102dd0100wxbi.html

    一、遇到的问题

    1.隐藏实现

    我们在给客户端提供接口的时候只希望能暴露它的接口,而隐藏它的实现或者算法。这时候,至少至少有两种选择:

    (1)写一个抽象类, 然后继承它

    (2)使用PIMPL, 把实现放到内部公有的文件里,而对外部隐藏起来

    2.重新编译

    当我们有一个很大的工程的时候,我们一个底层的头文件不希望被修改,因为这会导致包含该头文件的所有源文件都要重新编译。

    二、什么是PIMPLl机制

    1.Private Implementation

    直接的字面意思就是“实现私有化”,也如我们常常听到诸如“不要改动你的公有接口”这样的建议,Pimpl机制,顾名思义,将实现私有化,力图使得头文件对改变不透明。主要作用是解开类的使用接口和实现的耦合。

    2.pointer to implementation

    这种说法语义上更关注代码的实现方法,也就是一个指向实现的指针。

    3.桥接模式

    其实,这也是一个简单的桥接模式

    三、具体分析

    1.不使用PIMPL的情况

    1: //base.h
    2: class Base
    3: {
    4: public:
    5: void foo();
    6: };
    7:
    8: //sub.h
    9: #include "base.h"
    10: class sub : public Base
    11: {
    12: public:
    13: void go();
    14: };

    可以看到,如果有base中加了一个新的成员函数或者只要做过改动,那么它的子类sub这个文件都是重新编译才行。在一个大工程中,这样的修改可能导致重新编译时间的激增。

    2.一个稍好点的方法

    一般来说,不在头文件中包含头文件是一个比较好的习惯,但是这也不能完全消除修改base.h带来的重新编译代价。一个稍好点的方法就是只在sub.cpp中包含base.h,但这还是要重新编译的,只是在表现上更完美了一些。

    3.使用机制的情况

    我们使用前置声明一个Impl类,并将这个类的一个指针实例放入主类中。之后我们只修改Impl类内部私有的内容。

    1: //base.h
    2: class Imp;
    3: class Base
    4: {
    5: public:
    6: void foo();
    7: private:
    8: Imp* pImp;
    9: };

    除非我们修改base的公有接口,否则这个头文件是不会被修改了。然后,我们用这个Impl类的实现来完成主类的细节实现,在主类的构造函数中,我们完成了实现类指针的实例化:

    1: //cpp中包含实现类的头文件
    2: #include "imp.h"
    3:
    4: Base::Base()
    5: :pImp(new Imp)
    6: {
    7: }
    8:
    9: //调用实现类
    10: Base::foo()
    11: {
    12: pImp->foo();
    13: }
    14: Base::~Base()
    15: {
    16: try
    17: {
    18: delete pImp;
    19: }
    20: catch (...)
    21: {
    22: }
    23: }
    24:
    25: //这是真正的实现
    26: Imp::foo()
    27: {
    28: //do...xxx
    29: }

    4.实践

    在实践中,常常采用内部类来完成Pimpl机制

    //一个网上的例子

    1: // header
    2: class fruit
    3: {
    4: public:
    5: private:
    6: class impl;
    7: impl* pimpl_;
    8: }
    9:
    10: // implementation
    11: class fruit::impl
    12: {
    13:
    14: };
    15:
    16: fruit::fruit()
    17: {
    18: pimpl_ = new impl();
    19: }

    四、引来的其它的问题

    1.效率问题,每一个类的增加,肯定会增加开销。

    2.这种机制不一定就是最好的机制,最简单的机制才是最好的机制

    3.在构造和析构的时候,由于我们要new并且要保证delete,会引出RAII原则中的资源管理类的拷贝行为问题

    所以,更好的办法是我们在使用这个机制的时候可以使用一个比较安全的智能指针,比如scoped_ptr和shared_ptr,但shared_ptr通常更合适,因为它支持拷贝和赋值。

    1: class sample
    2: {
    3: private:
    4: class impl; //不完整的内部类声明
    5: shared_ptr<impl> p; //shared_ptr成员变量
    6: public:
    7: sample(); //构造函数
    8: void print(); //提供给外界的接口
    9: };
    10:
    11: //在sample的cpp中完整定义impl类和其他功能:
    12: class sample::impl //内部类的实现
    13: {
    14: public:
    15: void print()
    16: {
    17: cout << "impl print" << endl;
    18: }
    19: };
    20:
    21: //构造函数初始化shared_ptr void sample::print()
    22: sample::sample():p(new impl){}
    23: {
    24: //调用pimpl实现print()
    25: p->print();
    26: }

    直接可以用:

    1: sample s;
    2: s.print();

    (在more effective C++ Item M14中有这个问题的讨论,之后我们再做学习讨论)

  • 相关阅读:
    LeetCode子集问题
    面试题-求最大字典区间
    链表快速排序
    树的非递归遍历
    快速排序非递归实现
    leetcode217 python3 72ms 存在重复元素
    leetcode121 C++ 12ms 买股票的最佳时机 只能买卖一次
    leetcode1 python3 76ms twoSum 360面试题
    leetcode485 python3 88ms 最大连续1的个数
    leetcode119 C++ 0ms 杨辉三角2
  • 原文地址:https://www.cnblogs.com/senior-engineer/p/9811937.html
Copyright © 2011-2022 走看看