Скрыть реализацию с помощью указателя (идиома Pimpl)

Возможно ли, чтобы выполнить следующее:

x.hpp - этот файл включен многими другими классами

class x_impl; //forward declare
class x {
    public:
        //methods...
    private:
        x_impl* impl_;
};

x.cpp - реализация

#include <conrete_x>
typedef concrete_x x_impl;    //obviously this doesn't work
//implementation of methods...

В принципе, я хочу, чтобы пользователи включали файл x.hpp, но не знали о заголовке conrete_x.hpp.

Так как я могу использовать concrete_x только указателем и он появляется только как частный элемент данных, для компилятора должно быть достаточно прямого объявления, чтобы узнать, сколько места для его подготовки. Это похоже на известную "идиому прыгуна".

Можете ли вы мне помочь?

PS. Я не хочу использовать void* и отбрасывать его вокруг.

Ответ 1

Собственно, даже можно полностью скрыться от пользователя:

// Foo.hpp
class Foo {
public:

    //...

private:
    struct Impl;
    Impl* _impl;
};

// Foo.cpp
struct Foo::Impl {
    // stuff
};

Я хотел бы напомнить вам, что:

  • вам нужно будет написать правильный деструктор
  • и, следовательно, вам также понадобится правильный конструктор копирования, оператор присваивания копии, оператор перемещения и оператор назначения перемещения

Есть способы автоматизировать PIMPL ценой какой-то черной магии (похожей на то, что делает std::shared_ptr).

Ответ 2

В качестве альтернативы ответа от @Angew, если имя concrete_x не должно быть известно пользователям класса x, вы можете сделать это:

в x.hpp

class x_impl;
class x {
  public:
    x();
    ~x();
    //methods...
  private:
    x_impl* impl_;
};

в x.cpp

#include <concrete_x>
class x_impl : public concrete_x { };

x:x() : impl_(new x_impl) {}
x:~x() { delete impl_; }

Ответ 3

Это будет работать, только если декларация forward объявляет фактическое имя класса. Поэтому либо измените x.hpp на:

class concrete_x;
class x {
    public:
        //methods...
    private:
        concrete_x* impl_;
};

или используйте имя x_impl для класса, определенного в заголовке <concrete_x>.

Ответ 4

Для чего нужны интерфейсы. Определите интерфейс (чистый виртуальный класс) в общем файле заголовка и дайте его пользователям. Наследуйте свой конкретный класс от интерфейса и поместите его в не общий файл заголовка. Реализовать конкретный класс в файле cpp (вы даже можете определить конкретный класс внутри cpp).