WEBVTT

00:00.090 --> 00:00.630
Hello again!

00:00.930 --> 00:04.410
In this video, we are going to look at the so-called "pImpl" idiom.

00:05.880 --> 00:11.120
There are various ways you can implements the Handle-Body pattern: the pImpl idiom is a very

00:11.130 --> 00:12.060
popular way of doing it.

00:12.690 --> 00:19.110
The handle class, which has the interface, has a private member, which is a pointer to a body object,

00:19.110 --> 00:20.760
which has the actual implementation.

00:21.540 --> 00:24.870
So that is a "pointer to implementation" or "pImpl".

00:25.320 --> 00:29.760
And pronouncing it as "pimple" is easier and slightly more amusing.

00:30.810 --> 00:38.340
It's also known as a compiler firewall, because it introduces a rigid separation between implementation

00:38.340 --> 00:39.780
code and client code.

00:40.230 --> 00:45.000
So that reduces the amount of recompilation that is needed if the implementation gets changed.

00:51.470 --> 00:52.640
In the Handle class,

00:52.640 --> 00:54.590
there is a pointer to the Body.

00:55.340 --> 01:00.710
We do not need the full definition of the Body class for this, just a forward declaration.

01:01.070 --> 01:03.170
So the compiler knows that Body is a class.

01:05.320 --> 01:09.680
So this means we do not need to include the Body header in this Handle

01:09.700 --> 01:10.030
header.

01:13.050 --> 01:16.770
The interface is just going to forward to the Body interface.

01:17.160 --> 01:22.680
So when we call a member function here, this will call the corresponding member function on the Body.

01:25.840 --> 01:29.110
The Handle class is implemented using RAII.

01:29.680 --> 01:35.650
The constructor allocates the Body object and the destructor releases it. And we may need to think about

01:35.650 --> 01:38.800
what happens if objects of this class are copied or moved.

01:41.040 --> 01:45.240
So I have written a very simple example, this is a very basic Date class.

01:45.870 --> 01:47.100
This is going to be the Body.

01:47.370 --> 01:49.740
So it is going to be the implementation of the Date.

01:51.770 --> 01:54.740
The Handle class will be the interface to Date.

01:55.160 --> 01:58.370
So this is the Date class, as seen by the rest of the code.

01:59.240 --> 02:01.430
It has our pointer to the implementation.

02:02.180 --> 02:07.070
We have the forward declaration of the implementation class, but no other information about it.

02:09.280 --> 02:15.520
In the constructor, we call new, to allocate the implementation pointer, and then we call delete in the

02:15.530 --> 02:18.640
destructor. And then the member functions

02:18.850 --> 02:23.350
just call the corresponding member functions in the Date implementation.

02:25.550 --> 02:29.180
And finally, we have a very simple main() program, to test this out.

02:30.770 --> 02:31.310
And there we are.

02:32.060 --> 02:37.370
So if the client sees this Date_impl in the header and tries to use it, what do you think would

02:37.370 --> 02:37.670
happen?

02:40.810 --> 02:45.910
Undefined class! So the client only knows the name, but it cannot compile against it, because it does not have

02:45.910 --> 02:47.560
the full class definition.

02:48.040 --> 02:50.890
So we have a pointer, and a call to new.

02:51.400 --> 02:53.140
Could we use a unique_ptr here?

02:54.130 --> 02:55.480
And yes, we can.

02:55.840 --> 02:58.320
But there are a couple of things we need to bear in mind.

03:00.700 --> 03:05.590
So we can put a unique_ptr to Body, instead of having a traditional pointer to a Body.

03:06.070 --> 03:08.770
And this will manage the memory allocation for us.

03:09.760 --> 03:12.640
However, this code is not going to compile as it stands.

03:13.240 --> 03:19.180
We need to add a destructor to the Handle class and that will make it a so-called "complete type".

03:20.890 --> 03:25.690
Then if we do have a constructor, that means the compiler is not going to synthesize move operators.

03:26.290 --> 03:28.870
So if we want those, we need to provide them ourselves.

03:29.620 --> 03:34.570
And also, we do not get copy operators, because the class has a member which cannot be copied.

03:35.110 --> 03:36.520
unique_ptr is move-only.

03:40.360 --> 03:47.410
So let's try that out. So we can use the same Date implementation and also the same client. In the Date

03:47.410 --> 03:47.650
header,

03:47.650 --> 03:50.440
we now have a unique_ptr to the implementation.

03:51.430 --> 03:55.810
We also have the destructor. And we have added some move operators.

03:58.490 --> 04:04.340
In the constructor, we call make_unique() to initialize the implementation pointer. And the arguments

04:04.340 --> 04:07.760
for the constructor are going to be forwarded to the implementation.

04:09.030 --> 04:14.040
And then for the destructor and move operators, we can just default those, unless we want to do something

04:14.040 --> 04:14.490
special.

04:15.150 --> 04:17.160
And then we have the same member functions again.

04:19.680 --> 04:20.190
And then we are.

04:25.240 --> 04:27.490
We have already looked at the advantages of this.

04:27.490 --> 04:30.750
We do not need to have the clients including Body.h.

04:31.630 --> 04:35.590
If the implementation changes, we do not need to modify or recompile the clients.

04:36.100 --> 04:42.700
And if we are shipping the implementation as a DLL, then users do not need to install a new program

04:42.700 --> 04:43.330
executable.

04:44.560 --> 04:47.950
It is also useful for hiding the implementation from clients.

04:48.640 --> 04:53.680
So if you have some code which is super secret or commercially sensitive, then no one will be able

04:53.680 --> 04:54.910
to find out what it is.

04:57.120 --> 04:58.200
The disadvantages.

04:58.200 --> 05:01.230
This requires an extra memory allocation for the Body object.

05:01.650 --> 05:04.110
So your program is going to use slightly more memory.

05:05.040 --> 05:10.020
If you call a member function, then it is going to dereference a pointer

05:10.170 --> 05:15.600
to call the Body member function. So that will make your program run slightly more slowly.

05:16.620 --> 05:21.930
Loading a shared library will increase the time that it takes your program to start up. And this approach

05:21.930 --> 05:23.760
makes the codebase more complex.

05:25.510 --> 05:27.310
So where can you use pImpl?

05:27.730 --> 05:35.440
pImpl is often used in large projects, to reduce completion time and simplify product updates. It is also

05:35.470 --> 05:37.810
used a lot by QT, or "cute",

05:37.840 --> 05:41.350
I think it is pronounced. Which is a popular graphics library.

05:45.090 --> 05:50.220
Another possibility is that you could use pImpl to implement the container classes in the standard

05:50.550 --> 05:53.640
template library. For example, with the string class.

05:54.000 --> 06:00.270
This could have a body which stores the character data, and does the actual manipulation of the data.

06:00.840 --> 06:06.360
And then the Handle class, which provides the interface of the string class to the rest of the code.

06:07.500 --> 06:12.930
And if you are very careful, you could arrange for two string objects which share the same data to

06:12.930 --> 06:14.330
share the same Body pointer.

06:14.910 --> 06:18.450
So you could use that for reducing the amount of memory that your program consumes,

06:19.020 --> 06:23.910
if you have a lot of duplicated string objects. Obviously you have to be careful with that, especially

06:23.910 --> 06:24.840
if threads are involved.

06:25.380 --> 06:27.090
But that is something to think about.

06:28.460 --> 06:28.760
Okay.

06:28.760 --> 06:29.840
So that is it for this video.

06:30.290 --> 06:31.110
I will see you next time.

06:31.110 --> 06:33.440
But until then, keep coding!
