Я хочу избежать перекомпиляции всего, что включает в себя открытый заголовочный файл, просто потому, что что-то изменилось в закрытой части определения класса. Я изучаю другие варианты, кроме PIMPL.
Вот что я попробовал:
Я создал библиотеку, которая содержит класс A:
A_p.h содержит частную часть класса A
void PrivateMethod(int i);
хиджры публичный заголовочный файл:
class A
{
public:
A();
virtual ~A();
// other public members
private:
#ifdef A_PRIVATE
#include "A_p.h"#endif
};
a.cpp
#define A_PRIVATE
#include "A.h"
A::A() {}
A::~A() {}
void A::PrivateMethod(int i) { }
Затем я создал консольный проект Win32, который включает публичный заголовок (A.h) и ссылки на файл .lib.
Кажется, все работает, но меня интересуют любые подводные камни на этом пути. Кто-нибудь может уточнить это?
Абстрактные классы позволяют вам объявлять открытый интерфейс, но иметь личные данные и функции путем создания подкласса абстрактного класса.
Основная причина, по которой это не будет работать так, как вы описываете, и, следовательно, не поддерживается в стандарте C ++, заключается в том, что предложенное вами публичное объявление делает невозможным определение размера A
, Публичная декларация не показывает, сколько места необходимо для личных данных. Поэтому код, который видит только публичное объявление, не может быть выполнен new A
, не может выделить место для определения массива A
и не может делать арифметику с указателями на A
,
Существуют и другие проблемы, которые могут быть решены каким-либо образом, например, где расположены указатели на элементы виртуальных функций. Однако это вызывает ненужные осложнения.
Чтобы создать абстрактный класс, вы объявляете по крайней мере одну виртуальную функцию в классе. Виртуальная функция определяется с = 0
вместо тела функции. Это говорит о том, что у него нет реализации, и, следовательно, не может быть объекта абстрактного класса, кроме как подобъекта класса, производного от него.
Затем в отдельном приватном коде вы объявляете и определяете класс B
что происходит от A
, Вам нужно будет предоставить способы создания и уничтожения объектов, вероятно, с помощью публичной «новой» функции, которая возвращает указатель на A
и работает, вызывая частную функцию, которая может видеть объявление B
и публичная функция «удаления», которая принимает указатель на A
и работает, вызывая частную функцию, которая может видеть объявление B
,
«Кажется, все работает» — кажется там важно. Вы просто испытываете неопределенное поведение. Это плохо сформированная программа — определение класса должно быть одинаковым для всех модулей компиляции, использующих этот класс.
Поскольку это UB, может показаться, что он работает, но попробуйте объявить virtual
метод в частном разделе, и вы, скорее всего, столкнетесь с некоторыми видимыми проблемами.
Есть 3 хороших способа скрыть такую информацию:
только вперед объявить твой класс. Это работает только в том случае, если оно просто передается через клиентский код (через указатели и / или ссылки), и только используемый внутри вашей библиотеки Ваша библиотека должна будет предоставить фабричные функции или аналогичные для возврата указателя / ссылки в первую очередь, клиент никогда не сможет вызвать new
или же delete
выставлять абстрактный базовый класс, и снова предоставить фабричные функции (которые создают конкретный производный класс, видимый только внутри вашей библиотеки)
использование Pimpl
Я также согласен с тем, что вам следует пересмотреть любой класс, настолько большой, что скрывать его необходимо, но если вы действительно не можете его разбить, это ваши варианты.
Что касается того, как & почему нарушение ODR ломает вещи на практике: