C++中的PImpl Idiom的简介

C++中的PImpl Idiom的简介

[TOC]
在《effective C++》中了解到了PImpl Idiom模式, 搜索了解了一下, 有一篇英文介绍简洁易懂, 遂分享出来, 原文.

PImpl Idiom in C++ with Examples

背景描述
只要被包含的头文件出现哪怕多小的变动, 所有依赖于它的文件都必须要重新编译, 效率低下, 解决这一问题的一个方案为 PImpl Idiom.

概念介绍
PImpl Idiom 是一种接口与实现分离的技术. 它通过把 private data members 移动到分离的类中, 然后通过指针访问它, 来实现最小地暴露头文件减少构建依赖.

Header-and-Implementation-file-structure-within-the-PImpl-Idiom.jpg

如何实施方案 How to implement:

  • 创建一个新 class ( or struct ).
  • 把原类中所有的 private members 放入新类中.
  • 在头文件中定义一个指向新类的指针(智能指针是不错的选择).
  • 在头文件中前向声明新类/指向新类的指针.
  • 定义好新类里的析构函数,copy 构造函数,assignment operator.

明确定义析构/ copy 构造函数/ assignment operator 的原因: 方便智能指针( 例如 std::unique_ptr )检查可见的析构函数, 以及对于无法处理 copy /赋值的std::unique_ptr 而言, copy 构造函数/ assignment operator 也是必不可少的.

实例 Example:

User class 仅为接口以及数据结构体智能指针. 因此实现的任何变化只会影响 User.cpp, 其他用到 User class 的不需要重新编译.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/* |INTERFACE| User.h file */

#pragma once
#include <memory> // PImpl
#include <string>
using namespace std;

class User {
public:
// Constructor and Destructors

~User();
User(string name);

// Assignment 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<Impl> pimpl;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/* |IMPLEMENTATION| User.cpp file */

#include "User.h"
#include <iostream>
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;
}

优点 Advantages of PImpl:

  • 二进制兼容性: 二进制接口与私有数据段独立, 改变实现的数据不会破坏依赖其的代码.
  • 编译时间: 前面介绍的不用重新编译.
  • 数据隐藏: 接口与实现独立的必然结果.

缺点 Disadvantages of PImpl:

  • 内存管理: 增加内存使用量大小.
  • 维护量增加: 复杂了, 维护起来费力一些.
  • 继承性: 被隐藏起来的实现无法被继承, 接口虽然可以被继承.

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

作者

cx

发布于

2021-11-22

更新于

2022-07-16

许可协议