Базовая формулировка Pimpl Idiom
Пускай у нас есть класс, который выполняет определенную фильтрацию изображения. Для определенности положим, что это фильтр удаления шумов. Для этого нам нужен будет видимый класс NoiseReductionFilter для использования функциональности фильтра, и класс имплементации NoiseReductionFilterImpl, который будет инкапсулировать конкретную реализацию фильтра. Зачем нам вообще нужно такое разделение? Этим классом будет пользоваться потенциально много народу, поэтому мы не хотим раскрывать хоть какие-нибудь детали реализации, чтобы люди не делали своих предположений о реализации и не делали опасных низкоуровневых трюков. Это может навредить нашей интеллектуальной собственности или репутации проекта, если его будут неправильно использовать. Причина немного надутая, но большие проекты просто обязаны заботиться о таких вещах. Окей пишем(осторожно псевдокод):
В чем проблема этого кода? Для начала, мы тут вообще ничего не скрыли. Тип NoiseReductionFilterImpl должен быть определен в момент компиляции и определен он в известном файле NoiseReductionFilterImpl.hpp, к котором все, кому ни попадя имеют доступ. Ни о какой конфиденциальности речи быть не может.
Image processing - очень быстро развивающаяся область. Сейчас все больше проектов переходят с консервативных методов к использованию нейросетей. Поэтому, очевидно, что этот класс тоже будет довольно активно развиваться. А мы хотим поддерживать ABI совместимость с проектами пользователей и не ломать их проекты своими новыми версиями. В данном случае такого не получится сделать, потому что очень большой перечень изменений может сломать ABI и реально поменять реализацию без поломки бинарной совместимости невозможно.
Какое здесь может быть решение проблемы?
Убрать подключение NoiseReductionFilterImpl.hpp и сделать закрытый член класса указателем на тип NoiseReductionFilterImpl. Но раз мы убрали заголовочник с объявлением типа, тогда мы не можем использовать указатель на этот тип. Или можем?
Еще как можем. Есть такое понятие, как forward declaration. Мы можем сказать компилятору, что есть вот такой класс NoiseReductionFilterImpl и мы даём слово, что опишем и определим его, но пока не скажем тебе где. И тогда мы можем объявить приватный член класса, как указатель на NoiseReductionFilterImpl, но никак не использовать его. И нам это сойдет с рук. Компилятор потом сам отыщет определение этого класса и удачно разрезолвит все символы. Сейчас покажу, как это будет выглядеть.
Скажу сразу, этот код нельзя использовать как он есть здесь. Он просто, чтобы показать концепцию, и не использует необходимых фичей новых стандартов. Что здесь происходит. Мы вынесли определение в файл реализации. А значит никто, кроме нас не сможет увидеть или даже намек почуять, как реализована функциональность (не берем в расчет реверс-инженеров). Это будут просто инструкции, которые будут подгружаться во время выполнения кода клиента.
Обычно файл NoiseReductionFilter.hpp не меняется, так как это публичное апи, а указатель на реализацию дает нам возможность вертеть имплементацией как нам хочется.
Но есть и негативные последствия использования идиомы.
Продолжение в комментах…
#cppcore #design #howitworks #memory
Пускай у нас есть класс, который выполняет определенную фильтрацию изображения. Для определенности положим, что это фильтр удаления шумов. Для этого нам нужен будет видимый класс NoiseReductionFilter для использования функциональности фильтра, и класс имплементации NoiseReductionFilterImpl, который будет инкапсулировать конкретную реализацию фильтра. Зачем нам вообще нужно такое разделение? Этим классом будет пользоваться потенциально много народу, поэтому мы не хотим раскрывать хоть какие-нибудь детали реализации, чтобы люди не делали своих предположений о реализации и не делали опасных низкоуровневых трюков. Это может навредить нашей интеллектуальной собственности или репутации проекта, если его будут неправильно использовать. Причина немного надутая, но большие проекты просто обязаны заботиться о таких вещах. Окей пишем(осторожно псевдокод):
// NoiseReductionFilter.hpp
#include "NoiseReductionFilterImpl.hpp"
struct NoiseReductionFilter {
Filter();
private:
NoiseReductionFilterImpl impl;
};
// NoiseReductionFilter.cpp
NoiseReductionFilter::Filter() {
impl.FilterImpl();
}
В чем проблема этого кода? Для начала, мы тут вообще ничего не скрыли. Тип NoiseReductionFilterImpl должен быть определен в момент компиляции и определен он в известном файле NoiseReductionFilterImpl.hpp, к котором все, кому ни попадя имеют доступ. Ни о какой конфиденциальности речи быть не может.
Image processing - очень быстро развивающаяся область. Сейчас все больше проектов переходят с консервативных методов к использованию нейросетей. Поэтому, очевидно, что этот класс тоже будет довольно активно развиваться. А мы хотим поддерживать ABI совместимость с проектами пользователей и не ломать их проекты своими новыми версиями. В данном случае такого не получится сделать, потому что очень большой перечень изменений может сломать ABI и реально поменять реализацию без поломки бинарной совместимости невозможно.
Какое здесь может быть решение проблемы?
Убрать подключение NoiseReductionFilterImpl.hpp и сделать закрытый член класса указателем на тип NoiseReductionFilterImpl. Но раз мы убрали заголовочник с объявлением типа, тогда мы не можем использовать указатель на этот тип. Или можем?
Еще как можем. Есть такое понятие, как forward declaration. Мы можем сказать компилятору, что есть вот такой класс NoiseReductionFilterImpl и мы даём слово, что опишем и определим его, но пока не скажем тебе где. И тогда мы можем объявить приватный член класса, как указатель на NoiseReductionFilterImpl, но никак не использовать его. И нам это сойдет с рук. Компилятор потом сам отыщет определение этого класса и удачно разрезолвит все символы. Сейчас покажу, как это будет выглядеть.
// NoiseReductionFilter.hpp
struct NoiseReductionFilter {
Filter();
private:
struct NoiseReductionFilterImpl; // forward declaration
NoiseReductionFilterImpl * impl;
};
// NoiseReductionFilter.cpp
struct NoiseReductionFilter::NoiseReductionFilterImpl {
// implement functionality
};
NoiseReductionFilter::NoiseReductionFilter() : impl (new NoiseReductionFilter::NoiseReductionFilterImpl){}
NoiseReductionFilter::~NoiseReductionFilter() {
delete impl;
impl = nullptr;
}
void NoiseReductionFilter::filter() {
impl->FilterImpl();
}
Скажу сразу, этот код нельзя использовать как он есть здесь. Он просто, чтобы показать концепцию, и не использует необходимых фичей новых стандартов. Что здесь происходит. Мы вынесли определение в файл реализации. А значит никто, кроме нас не сможет увидеть или даже намек почуять, как реализована функциональность (не берем в расчет реверс-инженеров). Это будут просто инструкции, которые будут подгружаться во время выполнения кода клиента.
Обычно файл NoiseReductionFilter.hpp не меняется, так как это публичное апи, а указатель на реализацию дает нам возможность вертеть имплементацией как нам хочется.
Но есть и негативные последствия использования идиомы.
Продолжение в комментах…
#cppcore #design #howitworks #memory