📜  C++中的PImpl成语示例

📅  最后修改于: 2021-05-30 11:11:53             🧑  作者: Mango

对头文件进行更改时,包括它的所有源都需要重新编译。在大型项目和库中,由于即使对实现进行了很小的更改,每个人都必须等待一段时间才能编译代码,这可能会导致构建时间问题。解决此问题的一种方法是使用PImpl Idiom ,它将实现隐藏在标头中,并包括一个可立即编译的接口文件

PImpl成语(实现的指针)是一种用于将实现与接口分离的技术。它通过将私有数据成员移到单独的类中并通过不透明指针访问它们,最大程度地减少了标头暴露,并帮助程序员减少了构建依赖性。

实施方法:

  1. 创建一个单独的类(或struct)以实现
  2. 将所有私有成员从头到该类。
  3. 在头文件中定义一个实现类(Impl)。
  4. 在头文件中,创建指向实现类的前向声明(指针)
  5. 定义一个析构函数和一个复制/赋值运算符

显式声明析构函数的原因是,在编译时,智能指针( std :: unique_ptr )检查类型定义中是否存在可见的析构函数,如果仅向前声明,则会引发编译错误。

使用智能指针是一种更好的方法,因为指针可以控制PImpl的生命周期。

例子:

  • 头文件中包含的类定义是该类的公共接口。
  • 我们定义唯一的指针而不是原始的指针,因为接口类型的对象负责对象的生命周期。
  • 由于std :: unique_ptr是完整类型,因此需要用户声明的析构函数和复制/赋值运算符才能使实现类完整。
  • 从用户的角度来看,简单的方法是透明的。在内部,对IMPLementation结构所做的更改仅影响包含它的文件(User.cpp) 。这意味着用户无需重新编译即可应用这些更改。
    Header file
    /* |INTERFACE| User.h file */
      
    #pragma once
    #include  // PImpl
    #include 
    using namespace std;
      
    class User {
    public:
        // Constructor and Destructors
      
        ~User();
        User(string name);
      
        // Asssignment Operator and Copy Constructor
      
        User(const User& other);
        User& operator=(User rhs);
      
        // Getter
        int getSalary();
      
        // Setter
        void setSalary(int);
      
    private:
        // Internal implementation class
        class Impl;
      
        // Pointer to the internal implementation
        unique_ptr pimpl;
    };


    Implementation file
    /* |IMPLEMENTATION| User.cpp file */
      
    #include "User.h"
    #include 
    using namespace std;
      
    struct User::Impl {
      
        Impl(string name)
            : name(name){};
      
        ~Impl();
      
        void welcomeMessage()
        {
            cout << "Welcome, "
                 << name << endl;
        }
      
        string name;
        int salary = -1;
    };
      
    // Constructor connected with our Impl structure
    User::User(string name)
        : pimpl(new Impl(name))
    {
        pimpl->welcomeMessage();
    }
      
    // Default Constructor
    User::~User() = default;
      
    // Assignment operator and Copy constructor
      
    User::User(const User& other)
        : pimpl(new Impl(*other.pimpl))
    {
    }
      
    User& User::operator=(User rhs)
    {
        swap(pimpl, rhs.pimpl);
        return *this;
    }
      
    // Getter and setter
    int User::getSalary()
    {
        return pimpl->salary;
    }
      
    void User::setSalary(int salary)
    {
        pimpl->salary = salary;
        cout << "Salary set to "
             << salary << endl;
    }


    PImpl的优点:

    • 二进制兼容性:二进制接口独立于私有字段。对实现进行更改不会使相关代码停滞不前。
    • 编译时间:由于只需要重建实现文件而不是每个客户端都重新编译其文件,因此编译时间减少了。
    • 数据隐藏:可以轻松隐藏某些内部细节,例如实现技术和用于实现公共接口的其他库。

      PImpl的缺点:

    • 内存管理:由于分配的内存多于默认结构,因此内存使用量可能增加,这对于嵌入式软件开发至关重要。
    • 维护工作:由于为了使用pimpl和附加的指针间接调用而增加了类,因此维护变得更加复杂(只能通过指针/引用使用接口)
    • 继承:尽管类PImpl可以继承隐藏的实现。

      参考: https //en.cppreference.com/w/cpp/language/pimpl

      要从最佳影片策划和实践问题去学习,检查了C++基础课程为基础,以先进的C++和C++ STL课程基础加上STL。要完成从学习语言到DS Algo等的更多准备工作,请参阅“完整面试准备课程”