PImpl Idiom en C++ con ejemplos

Cuando se realizan cambios en un archivo de encabezado , todas las fuentes, incluido este, deben volver a compilarse. En proyectos y bibliotecas grandes, puede causar problemas de tiempo de compilación debido al hecho de que incluso cuando se realiza un pequeño cambio en la implementación, todos tienen que esperar un tiempo hasta que compilan su código. Una forma de resolver este problema es usando PImpl Idiom , que oculta la implementación en los encabezados e incluye un archivo de interfaz que se compila instantáneamente .

PImpl Idiom (Pointer to IMPLementation) es una técnica utilizada para separar la implementación de la interfaz. Minimiza la exposición del encabezado y ayuda a los programadores a reducir las dependencias de compilación al mover los miembros de datos privados en una clase separada y acceder a ellos a través de un puntero opaco .

Cómo implementar: 

  1. Cree una clase separada (o estructura) para la implementación
  2. Coloque todos los miembros privados del encabezado en esa clase.
  3. Defina una clase de implementación (Impl) en el archivo de encabezado.
  4. En el archivo de encabezado, cree una declaración directa (un puntero) que apunte a la clase de implementación.
  5. Defina un destructor y un operador de copia/asignación .

La razón para declarar explícitamente un destructor es que al compilar, el puntero inteligente ( std::unique_ptr ) verifica si en la definición del tipo existe un destructor visible y arroja un error de compilación si solo se declara hacia adelante.

El uso de un puntero inteligente es un mejor enfoque, ya que el puntero toma el control del ciclo de vida del PImpl.

Ejemplo: 

  • La definición de clase en el archivo de encabezado incluido es la interfaz pública de la clase.
  • Definimos un puntero único en lugar de uno sin procesar porque el objeto del tipo de interfaz es responsable de la vida útil del objeto.
  • Dado que std::unique_ptr es un tipo completo, requiere un destructor declarado por el usuario y operadores de copia/asignación para que la clase de implementación esté completa.
  • El enfoque de la espinilla es transparente desde el punto de vista del usuario. Los cambios realizados en la estructura IMPLementation, internamente, afectan solo al archivo que lo contiene (User.cpp) . Esto significa que el usuario no necesita volver a compilar para que se apliquen estos cambios.

Header file

/* |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;
};

Implementation file

/* |IMPLEMENTATION| User.cpp file */
 
#include "User.h"
#include <iostream>
using namespace std;
 
struct User::Impl {
 
    Impl(string name)
        : name(move(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(move(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;
}

Ventajas de PImpl:

  • Compatibilidad binaria : la interfaz binaria es independiente de los campos privados. Hacer cambios en la implementación no rompería el código dependiente.
  • Tiempo de compilación : el tiempo de compilación disminuye debido al hecho de que solo se debe reconstruir el archivo de implementación en lugar de que cada cliente vuelva a compilar su archivo.
  • Ocultación de datos : puede ocultar fácilmente ciertos detalles internos, como técnicas de implementación y otras bibliotecas utilizadas para implementar la interfaz pública.

Desventajas de PImpl:

  • Gestión de la memoria : posible aumento en el uso de la memoria debido a una mayor asignación de memoria que con la estructura predeterminada, lo que puede ser crítico en el desarrollo de software integrado.
  • Esfuerzo de mantenimiento : el mantenimiento se está volviendo más complejo debido a la clase adicional para usar pimpl y puntero indirecto adicional (la interfaz solo se puede usar a través de puntero/referencia) .
  • Herencia : la implementación oculta no se puede heredar, aunque una clase PImpl sí.

Referencia: https://en.cppreference.com/w/cpp/language/pimpl

Publicación traducida automáticamente

Artículo escrito por MihailYonchev y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *