Как проверить, что PIMPL не перекомпилирует класс клиента - PullRequest
1 голос
/ 16 марта 2020

Я пытаюсь понять идиому PIMPL.

У меня есть несколько файлов, скажем, "Implement.cpp / Implement.h", реализующих идиому PIMPL: он содержит интерфейс publi c и частную реализацию .
"Client.cpp / Client.h" использует интерфейс publi c.
Другой файл "main. cpp" просто использует класс клиента.
Я написал очень простой make-файл. Сначала все компилируется:

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

Я хотел бы сказать, что, если я изменю что-то в реализации PIMPL, клиент не будет перекомпилировать, и что, если я не буду использовать идиому PIMPL (Если я модификация в интерфейсе publi c), клиент перекомпилирует.

  • Вывод компилятора при изменении частной реализации:

    g ++ -std = c ++ 11 - c Реализация. cpp
    g ++ -o main main.o Реализация.o Client.o -std = c ++ 11

  • Вывод компилятора при публикации интерфейса c (новый метод, новый элемент с инициализацией и т. д. c) изменено:

    g ++ -std = c ++ 11 - c Реализация. cpp
    g ++ -o main main.o Реализация.o Client.o -std = c ++ 11

На самом деле, это то же самое.

Я ожидал, что если я изменю что-то в интерфейсе publi c, он должен перекомпилировать и "Реализация", и "Клиент" :

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

Что на самом деле делает компилятор, и как я могу убедиться, что компилятор компилирует только необходимое при использовании идиомы PIMPL?

EDIT (код добавлен):
Реализация. 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;} 

Реализация.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 Ответ

1 голос
/ 16 марта 2020

Что на самом деле делает компилятор

Компилятор делает то, что ему было сказано. Здесь:

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

... Реализация. cpp компилируется и связывается с ранее скомпилированными main.o и Client.o. Ни Client. cpp, ни main. cpp не скомпилированы.

Я ожидал, что если я изменю что-то в интерфейсе publi c, он должен перекомпилировать и "реализации", и "клиента"

Если вы изменили определение в реализации.h, то все блоки перевода, которые его включают, должны быть перекомпилированы. Если этого не сделано, а компоновщику предписано использовать несовместимый объектный файл, то весьма вероятно, что программа нарушит правило единого определения. Такое нарушение делает программу некорректной, но для диагностики этой проблемы не требуется языковая реализация. Некоторые нарушения обнаруживаются компоновщиком, другие - нет.

как проверить, что компилятор компилирует только необходимое при использовании идиомы PIMPL?

Из-за PIMPL, вам не нужно изменять заголовок, и поскольку вы не изменяете заголовок, вы знаете, что вам не нужно перекомпилировать единицы перевода, которые включают этот заголовок.

Сборка таких систем, как make и Обычно ниндзя отслеживает время изменения всех файлов, включенных в модуль перевода, и пропускает перекомпиляцию, когда время старше, чем ранее скомпилированный объектный файл. Проверка того, может ли такой инструмент перекомпилировать конкретный исходный файл, обычно может быть проверена путем проверки выходных данных инструмента.

Кроме того, если вы используете что-то вроде внешнего интерфейса ccache, то вы заметите, что перекомпиляция неизмененного модуля перевода намного быстрее, потому что на самом деле он не будет перекомпилирован из-за загрузки вместо этого из кэша. Ccache использует хешированное содержимое модуля перевода вместо времени модификации для обнаружения изменений.

...