What is pimpl
In simple, pimpl is a c++ technique that removes implementation details of a class from its object representation by placing them in a seperate class and accessing it via an opaque pointer
What the hell is that?
Let’s start with simple example.
1
2
3
4
5
6
7
8
9
10
11
12
// Student.h
class Student
{
public:
Student(const std::string& name);
~Student();
void addBook(const std::string& bookName);
void printAllBooks() const;
private:
std::string m_name;
std::vector<std::string> m_books;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Student.cpp
Student::Student(const std::string& name) : m_name(name)
{
}
Student::~Student()
{
m_name = "";
m_books.clear();
}
void Student::addBook(const std::string& bookName)
{
m_books.push_back(bookName);
}
void Student::printAllBooks() const
{
for (const auto& book : m_books) {
std::cout << book << "\n";
}
}
Now, what’s the problem. The problem is
- All private variables in
Student.h
can be seen to all the users of this header file - When header file is updated, especially when privates variables are added or removed or modified, Then all the source code which includes this header file get recompiled again
How can we avoid this?
This can be avoided using c++ pimpl, What we just need to do is two things,
- Create a forward declaration of the class with any name (say
Impl
) in header (Student.h
) and add uinque pointer to it as a private member - Move all the private variable to
Impl
class and this class defintion should be present inStudent.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
// Student.h
class Student
{
public:
Student(const std::string& name);
~Student();
void addBook(const std::string& bookName);
void printAllBooks() const;
private:
class Impl; //forward declaration
std::unique_ptr<Impl> m_pImpl = nullptr;
};
Now in above header file we can see that all the private variables are hidden and burried in the source (Student.cpp
) file. Only thing which is exposed or seen in the header file is that pointer to impl (Pointer to Implementation).
Let’s us look, how can we implement Impl
class
Student.cpp
looks like this
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
// Student.cpp
class Student::Impl
{
public:
Impl(const std::string& name) : m_name(name)
{
}
~Impl()
{
m_name = "";
m_books.clear();
}
void addBook(const std::string& bookName)
{
m_books.push_back(bookName);
}
void printAllBooks() const
{
for (const auto& book : m_books) {
std::cout << book << "\n";
}
}
private:
std::string m_name;
std::vector<std::string> m_books;
};
Student::Student(const std::string& name) : m_pImpl(std::make_unique<Impl>(name))
{
}
Student::~Student()
{
}
void Student::addBook(const std::string& bookName)
{
if (m_pImpl) {
m_pImpl->addBook(bookName);
}
}
void Student::printAllBooks() const
{
if (m_pImpl) {
m_pImpl->printAllBooks();
}
}
With this above approach we can avoid the exposion of the private details hidden from the header file and also compilation time can be imporved
What are the downfall of this approach
- Very first drawback is, as you had seen in the example, an unnecessary wrapper for each of the function we need to write. Once the program grows bigger and bigger, maintaining this functions become headace
- Simply we have introduced the pointer and allocated the memory in heap, which would have been much more simpler without pimpl
When can we go for pimpl implementation
- During the development of the security related functionalities, where we want to hide the private variables, which might be a thridy party variables or any security related data structures or variables
- When we dont want to break the binary interface which is dependent on private variables