2

I try to understand the PIMPL idiom.

I have several files, let's say "Implementation.cpp/Implementation.h" implement the PIMPL idiom: it contains a public interface and a private implementation.
"Client.cpp/Client.h" use the public interface.
Another file "main.cpp" just uses the client class.
I wrote a very simple makefile. At first, everything compiles:

g++ -std=c++11 -c main.cpp  
g++ -std=c++11 -c Implementation.cpp  
g++ -std=c++11 -c Client.cpp  
g++ -o main main.o Implementation.o Client.o -std=c++11

I wanted to prouve that if I modify something in the PIMPL implementation, the client will not recompile, and that if I do no use the PIMPL idiom (If I do a modification in the public interface) the client will recompile.

  • Compiler output when private implementation is modified:

    g++ -std=c++11 -c Implementation.cpp
    g++ -o main main.o Implementation.o Client.o -std=c++11

  • Compiler output when public interface (new method, new member with initialization, etc) is modified:

    g++ -std=c++11 -c Implementation.cpp
    g++ -o main main.o Implementation.o Client.o -std=c++11

Actually, it is the same.

My expectation was, if I modify something in the public interface it should recompile both "Implementation" and "Client":

g++ -std=c++11 -c Implementation.cpp  
g++ -std=c++11 -c Client.cpp  
g++ -o main main.o Implementation.o Client.o -std=c++11

What does the compiler really do, and how can I check that the compiler only compile the necessary when using PIMPL idiom?

EDIT (code added):
Implementation.cpp:

#include "Implementation.h"

class PublicInterface::PrivateImplementation
{
public:
  PrivateImplementation(std::string name) : name(name), id(0){};
  virtual ~PrivateImplementation(void){};
  std::string name; 
  int id; 
}; 
PublicInterface::PublicInterface(std::string name) : pImplPrivate(new PrivateImplementation(name)){} 
PublicInterface::~PublicInterface() = default; 
int PublicInterface::GetID(void) const { return this->pImplPrivate->id;} 
void PublicInterface::SetID(const int id) { this->pImplPrivate->id = id;} 

Implementation.h:

#include <memory>
#include <string> 

class PublicInterface
{
public:
  PublicInterface(std::string name); 
  virtual ~PublicInterface(void);
  int GetID(void) const; 
  void SetID(const int id);
private: 
  class PrivateImplementation;
  std::unique_ptr<PrivateImplementation> pImplPrivate;
};  

client.cpp:

#include <iostream>
#include "Client.h"
#include "Implementation.h"

Client::Client(void){}
Client::~Client(void){}
void Client::Caller(void)
{
  PublicInterface interface(std::string("Interface"));
  std::cout << "Interface ID " << interface.GetID() << std::endl;
  interface.SetID(5);
  std::cout << "Interface ID " << interface.GetID() << std::endl;
}

client.h:

class Client
{
    Client(void);
    virtual ~Client(void);
public:
    static void Caller(void);
    static void Another(void);
};

main.cpp:

#include "Client.h"

int main(int argc, char** argv)
{
    Client::Caller();
    return 0;
}

Makefile:

CPPFLAGS=-std=c++11

main : main.o Implementation.o Client.o
    g++ -o main main.o Implementation.o Client.o $(CPPFLAGS)

main.o : main.cpp
    g++ $(CPPFLAGS) -c main.cpp
Implementation.o : Implementation.cpp
    g++ $(CPPFLAGS) -c Implementation.cpp
Client.o : Client.cpp
    g++ $(CPPFLAGS) -c Client.cpp

clean :
    rm main main.o Implementation.o Client.o
  • 1
    I think your makefile might be interesting too. If you change `Client.cpp` or `Client.h`, at least `Client.cpp` should be recompiled, but it isn't according to the above. Does any of `Implementation.*` `#include "Client.h"`? – Ted Lyngmo Mar 16 '20 at 15:12
  • 1
    Please add your code. Post a [reproducable minimal example](https://stackoverflow.com/help/minimal-reproducible-example) – Superlokkus Mar 16 '20 at 15:39

1 Answers1

1

What does the compiler really do

The compiler does what it has been told to do. Here:

g++ -std=c++11 -c Implementation.cpp
g++ -o main main.o Implementation.o Client.o -std=c++11

... Implementation.cpp is compiled, and linked with previously compiled main.o and Client.o. Neither Client.cpp nor main.cpp are compiled.

My expectation was, if I modify something in the public interface it should recompile both "Implementation" and "Client"

If you did modify a definition in Implementation.h, then all translation units that include it must be recompiled. If it is not done, and the linker is told to use the incompatible object file, then it is highly likely that the program violates the One Definition Rule. Such violation makes the program ill-formed, but the language implementation is not required to diagnose this issue. Some violations are caught by the linker, others are not.

how can I check that the compiler only compile the necessary when using PIMPL idiom?

Because of PIMPL, you don't need to modify the header, and because you don't modify the header, you know that you don't need to recompile the translation units which include that header.

Build systems such as make and ninja generally keep track of the modification time of all files included in a translation unit, and skip recompilation when the times are older than the previously compiled object file. Checking whether such tool recompiles a particular source file can typically be checked by inspecting the output of the tool.

Furthermore, if you use something like the ccache frontend, then you'll notice that recompiling an unmodified translation unit is much faster because it won't actually be recompiled due to being loaded from the cache instead. Ccache uses hashed content of the translation unit instead of modification time to detect changes.

eerorika
  • 232,697
  • 12
  • 197
  • 326