Страница 1 из 1
C++ идиома pimpl
Добавлено: 29 май 2021, 11:15
Olej
Идиома pimpl в C++: Указатель на реализацию
19.09.2016
Идиома pimpl (pointer to implementation - указатель на реализацию) полезна в тех случаях, когда нам нужно что-то скрыть. Она обеспечивает еще более глубокий вид инкапсуляции, которая маскирует не просто реализацию, а также все ее зависимости.
Шаблон программирования Pimpl - то, что вам следует знать
Основы
Вы можете встретить шаблон Pimpl под другими именами: d-pointer, compiler firewall или даже шаблон Cheshire Cat или непрозрачный указатель.
В его основной форме шаблон выглядит следующим образом:
В классе мы перемещаем все закрытые члены в новый объявленный тип, например, в класс PrivateImpl .
Объявляем PrivateImpl в заголовочном файле основного класса.
В соответствующем файле cpp объявляем класс PrivateImpl и определяем его.
Теперь, если вы измените закрытую реализацию, код клиента не будет перекомпилирован (поскольку интерфейс не изменился).
Перевод статьи «Pimp My Pimpl», часть 1
В первой части статьи рассматривается классическая идиома Pimpl (pointer-to-implementation, указатель на реализацию), показываются её преимущества и рассматривается дальнейшее развитие идиом на её основе.
Вторая часть будет сосредоточена на том, как уменьшить недостатки, которые неизбежно возникают при использовании Pimpl.
Re: C++ идиома pimpl
Добавлено: 25 июн 2021, 15:28
Olej
Olej писал(а): ↑29 май 2021, 11:15
Идиома pimpl (pointer to implementation - указатель на реализацию) полезна в тех случаях, когда нам нужно что-то скрыть.
Делаем (полезный) демонстрационный пример:
- вычисление корня квадратного из числа (что может быть проще?)...
- но вычисление не одним методом, а для сравнения несколькими...
- в том числе и при сравнении времени (производительности) затрачиваемого на вычисление
Код: Выделить всё
#include <iostream>
#include <sstream>
#include <chrono>
#include <math.h>
#include "sqr.h"
using namespace std;
int main() {
const int rept = 1000000;
while (true) {
std::cout << "число [>0] (Enter - завершение): " << std::flush;
std::string s;
std::getline( cin, s );
if (s.empty())
break;
float val = 0;
std::istringstream( s ) >> val;
if (0 == val)
{
std::cout << "это не число" << std::endl;
continue;
}
else if (val < 0)
{
std::cout << "только положительные" << std::endl;
continue;
}
std::cout << "задан аргумент: " << val << std::endl;
long double etalon = sqrtl(val), real;
std::cout << "эталонный результат = " << etalon << std::endl;
while (true) {
std::cout << "метод [0.." << sqr::max_method() << "] (Enter - завершение методов): " << std::flush;
std::string s;
std::getline( cin, s );
if (s.empty())
break;
int m;
std::istringstream( s ) >> m;
sqr obj( m );
std::cout << "метод: " << obj.get_title() << std::endl;
real = (long double)obj.calculate(val);
std::cout << "SQRT(" << val << ") = " << real << " [" << ((real - etalon) / etalon * 100) << "%]" << std::endl;
auto begin = std::chrono::steady_clock::now();
for( int i = 0; i < rept; i++ )
obj.calculate(val);
auto end = std::chrono::steady_clock::now();
auto elapsed_mls = std::chrono::duration_cast<std::chrono::nanoseconds>(end - begin);
std::cout << "время " << rept << " вычислений [мксек] = " << elapsed_mls.count() / 1000 << "." << elapsed_mls.count() % 1000 << std::endl;
}
}
}
Программа знает (видит) только публичный интерфейс sqr.h ... но там нет деталей реализации.
Re: C++ идиома pimpl
Добавлено: 25 июн 2021, 15:34
Olej
Olej писал(а): ↑25 июн 2021, 15:28
Программа знает (видит) только публичный интерфейс sqr.h ... но там нет деталей реализации.
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ cat sqr.h
#include <string>
#include <vector>
class sqrimpl;
class sqr
{
public:
sqr(int); // метод вычисления
static int max_method(void); // max. индекс метода: 0...max_method
const std::string& get_title(); // название метода
float calculate(float); // вычмслить SQRT(...)
private:
sqrimpl* impl;
};
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ cat sqr.cc
#include "sqr.h"
#include "sqrimpl.h"
sqr::sqr(int m) : impl(sqrimpl::get_impl(m)) {}
int sqr::max_method(void) {return sqrimpl::max_method();}
const std::string& sqr::get_title() {return impl->get_title();}
float sqr::calculate(float val) {return impl->calculate(val);}
Но и здесь только описание публичного интерфейса (доступного приложениям), но нет никаких деталей реализации.
Все запросы к методам класса sqr транзитом ретранслируются в класс sqrimpl, про который мы видим только его название и наличие указателя на такой объект.
Re: C++ идиома pimpl
Добавлено: 25 июн 2021, 15:40
Olej
Olej писал(а): ↑25 июн 2021, 15:34
Все запросы к методам класса sqr транзитом ретранслируются в класс sqrimpl, про который мы видим только его название и наличие указателя на такой объект.
А вот здесь уже детали реализации:
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ cat sqrimpl.h
#include <string>
class sqrimpl
{
public:
sqrimpl(const char*);
static sqrimpl* get_impl(int m);
static int max_method(void);
virtual float calculate(float) = 0;
virtual const std::string& get_title() const;
protected:
std::string title;
private:
static sqrimpl* aimpl[];
};
//--------------------------------------------------------------------------------------------------------------------
class sqrimpl1 : public sqrimpl
{
public:
sqrimpl1(const char* name) : sqrimpl(name) {}
protected:
float calculate(float);
};
class sqrimpl2 : public sqrimpl
{
public:
sqrimpl2(const char* name) : sqrimpl(name) {}
protected:
float calculate(float);
};
class sqrimpl3 : public sqrimpl
{
public:
sqrimpl3(const char* name) : sqrimpl(name) {}
protected:
float calculate(float);
};
class sqrimpl4 : public sqrimpl
{
public:
sqrimpl4(const char* name) : sqrimpl(name) {}
protected:
float calculate(float);
};
class sqrimpl5 : public sqrimpl
{
public:
sqrimpl5(const char* name) : sqrimpl(name) {}
protected:
float calculate(float);
};
И, более того, реализация 5 способов ... некоторые из них совсем экзотические...
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$
olej@R420:~/2021/OWN_TEST.codes/p2sqr$
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ cat sqrimpl.cc
#include <math.h>
#include <cmath>
#include "sqrimpl.h"
//---------------------------------------------------------------------------------------------------------------------------
sqrimpl* sqrimpl::aimpl[] = {
new sqrimpl1("POSIX C API"),
new sqrimpl2("C++ API"),
new sqrimpl3("Fast Inversion"),
new sqrimpl4("exp (...ln)"),
new sqrimpl5("pow 1/2 ..."),
};
sqrimpl::sqrimpl(const char* ttl) {
title = std::string(ttl);
}
sqrimpl* sqrimpl::get_impl(int m)
{
if (m < 0 ) m = 0;
if ((unsigned)m >= sizeof(sqrimpl::aimpl) / sizeof(sqrimpl::aimpl[0])) m = 0;
return aimpl[m];
}
int sqrimpl::max_method(void)
{
return sizeof(sqrimpl::aimpl) / sizeof(sqrimpl::aimpl[0]) - 1;
}
const std::string& sqrimpl::get_title() const
{
return title;
}
//---------------------------------------------------------------------------------------------------------------------------
float sqrimpl1::calculate(float x) { return ::sqrt(x); }
//---------------------------------------------------------------------------------------------------------------------------
float sqrimpl2::calculate(float x) { return std::sqrt(x); }
//---------------------------------------------------------------------------------------------------------------------------
// https://habrahabr.ru/company/infopulse/blog/336110/?utm_campaign=email_digest&utm_source=email_habrahabr&utm_medium=email_week_20170829&utm_content=link2post
float sqrimpl3::calculate(float x) {
int i = *(int*)&x; // представим биты float в виде целого числа
i = 0x1fbd1df5 + ( i >> 1 );
float y = *(float*)&i;
y = y - ( y * y - x ) / 2. / y; // одна итерация уточнения (метод Ньютона)
return y;
}
//---------------------------------------------------------------------------------------------------------------------------
float sqrimpl4::calculate(float x) {
return ::exp(0.5 * ::log(x));
}
//---------------------------------------------------------------------------------------------------------------------------
float sqrimpl5::calculate(float x) {
return ::pow(x, 0.5);
}
//---------------------------------------------------------------------------------------------------------------------------
... экзотические - как, например, метод sqrimpl2, который раньше уже обсуждался здесь:
алгоритмические трюки.
Re: C++ идиома pimpl
Добавлено: 25 июн 2021, 15:43
Olej
Olej писал(а): ↑25 июн 2021, 15:40
А вот здесь уже детали реализации:
А теперь собираем всё это до кучи:
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ cat Makefile
CXX += -Wall -std=c++11 -pedantic
all: Makefile p2sqr
%.o: %.cc
$(CXX) -c $< -o $@
p2sqr: p2sqr.cc sqr.o sqrimpl.o
$(CXX) $< sqr.o sqrimpl.o -o $@
rm *.o
clean:
rm -f $(TASK) *.o
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ make
g++ -Wall -std=c++11 -pedantic -c sqr.cc -o sqr.o
g++ -Wall -std=c++11 -pedantic -c sqrimpl.cc -o sqrimpl.o
g++ -Wall -std=c++11 -pedantic p2sqr.cc sqr.o sqrimpl.o -o p2sqr
rm *.o
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ ./p2sqr
число [>0] (Enter - завершение): 1000000
задан аргумент: 1e+06
эталонный результат = 1000
метод [0..4] (Enter - завершение методов): 0
метод: POSIX C API
SQRT(1e+06) = 1000 [0%]
время 1000000 вычислений [мсек] = 18216.309
метод [0..4] (Enter - завершение методов): 1
метод: C++ API
SQRT(1e+06) = 1000 [0%]
время 1000000 вычислений [мсек] = 15577.270
метод [0..4] (Enter - завершение методов): 2
метод: Fast Inversion
SQRT(1e+06) = 1000.06 [0.00640259%]
время 1000000 вычислений [мсек] = 21773.480
метод [0..4] (Enter - завершение методов): 3
метод: exp (...ln)
SQRT(1e+06) = 1000 [1.2207e-05%]
время 1000000 вычислений [мсек] = 52177.820
метод [0..4] (Enter - завершение методов): 4
метод: pow 1/2 ...
SQRT(1e+06) = 1000 [0%]
время 1000000 вычислений [мсек] = 68135.661
метод [0..4] (Enter - завершение методов):
число [>0] (Enter - завершение):
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ ./p2sqr
число [>0] (Enter - завершение): 2
задан аргумент: 2
эталонный результат = 1.41421
метод [0..4] (Enter - завершение методов): 0
метод: POSIX C API
SQRT(2) = 1.41421 [-1.71143e-06%]
время 1000000 вычислений [мсек] = 18587.227
метод [0..4] (Enter - завершение методов): 1
метод: C++ API
SQRT(2) = 1.41421 [-1.71143e-06%]
время 1000000 вычислений [мсек] = 20240.423
метод [0..4] (Enter - завершение методов): 2
метод: Fast Inversion
SQRT(2) = 1.41557 [0.0957728%]
время 1000000 вычислений [мсек] = 22924.79
метод [0..4] (Enter - завершение методов): 3
метод: exp (...ln)
SQRT(2) = 1.41421 [-1.71143e-06%]
время 1000000 вычислений [мсек] = 52287.877
метод [0..4] (Enter - завершение методов): 4
метод: pow 1/2 ...
SQRT(2) = 1.41421 [-1.71143e-06%]
время 1000000 вычислений [мсек] = 65192.608
метод [0..4] (Enter - завершение методов):
число [>0] (Enter - завершение):
Re: C++ идиома pimpl
Добавлено: 25 июн 2021, 16:24
Olej
Olej писал(а): ↑25 июн 2021, 15:43
А теперь собираем всё это до кучи:
А вот теперь самое интересное...
Вы можете собрать sqrimpl.cc - в отдельную разделяемую (DLL) библиотеку ... (или статическую) ... абсолютно из тех же
всех исходных кодов (без изменений), только используя новый Makefile:
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ cat Makefile.dll
PROG = p2sqr
LNAME = sqr
LIB = lib$(LNAME).so
CXX += -Wall -std=c++11 -pedantic
all: $(LIB) $(PROG)
%.o: %.cc
$(CXX) -c $< -o $@
$(LIB): sqrimpl.cc sqrimpl.h
$(CXX) -c -fpic -fPIC -shared $< -o sqrimpl.o
$(CXX) -shared -o $(LIB) sqrimpl.o
rm -f sqrimpl.o
$(PROG): $(PROG).cc sqr.o $(LIB)
$(CXX) $< sqr.o -Bdynamic -L./ -l$(LNAME) -o $@
clean:
rm -f $(PROG) $(LIB) *.o
Сборка:
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ make -f Makefile.dll clean
rm -f p2sqr libsqr.so *.o
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ make -f Makefile.dll
g++ -Wall -std=c++11 -pedantic -c -fpic -fPIC -shared sqrimpl.cc -o sqrimpl.o
g++ -Wall -std=c++11 -pedantic -shared -o libsqr.so sqrimpl.o
rm -f sqrimpl.o
g++ -Wall -std=c++11 -pedantic -c sqr.cc -o sqr.o
g++ -Wall -std=c++11 -pedantic p2sqr.cc sqr.o -Bdynamic -L./ -lsqr -o p2sqr
И вообще не поставлять в комплекте приложения sqrimpl.cc (код реализации), а предоставлять готовую
бинарную библиотеку.
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ ls -l lib*
-rwxrwxr-x 1 olej olej 23832 июн 25 16:10 libsqr.so
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ ldd p2sqr
linux-vdso.so.1 (0x00007ffd6bffe000)
libsqr.so => /home/olej/2021/OWN_TEST.codes/p2sqr/libsqr.so (0x00007f6396451000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f6396256000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f6396107000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f63960ec000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6395efa000)
/lib64/ld-linux-x86-64.so.2 (0x00007f639645f000)
И всё выполняется так же:
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ export LD_LIBRARY_PATH=`pwd`; ./p2sqr
число [>0] (Enter - завершение): 3
задан аргумент: 3
эталонный результат = 1.73205
метод [0..4] (Enter - завершение методов): 3
метод: exp (...ln)
SQRT(3) = 1.73205 [-1.79482e-06%]
время 1000000 вычислений [мксек] = 55536.555
метод [0..4] (Enter - завершение методов):
число [>0] (Enter - завершение):
Ура!
Сбылась сокровенная мечта выньдаунов: теперь исходный код реализации можно не предоставлять!
Re: C++ идиома pimpl
Добавлено: 25 июн 2021, 16:48
Olej
Olej писал(а): ↑25 июн 2021, 16:24
(или статическую)
Опять
только меняем Makefile:
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$
cat Makefile.sta
PROG = p2sqr
LNAME = sqr
LIB = lib$(LNAME).a
CXX += -Wall -std=c++11 -pedantic
all: $(LIB) $(PROG)
%.o: %.cc
$(CXX) -c $< -o $@
$(LIB): sqrimpl.o
ar -q $(LIB) $<
rm -f $<
ar -t $(LIB)
$(PROG): $(PROG).cc sqr.o $(LIB)
$(CXX) $< sqr.o -Bstatic -L./ -l$(LNAME) -o $@
clean:
rm -f $(PROG) $(LIB) *.o *.a
Сборка:
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ make -f Makefile.sta
g++ -Wall -std=c++11 -pedantic -c sqrimpl.cc -o sqrimpl.o
ar -q libsqr.a sqrimpl.o
rm -f sqrimpl.o
ar -t libsqr.a
sqrimpl.o
sqrimpl.o
g++ -Wall -std=c++11 -pedantic -c sqr.cc -o sqr.o
g++ -Wall -std=c++11 -pedantic p2sqr.cc sqr.o -Bstatic -L./ -lsqr -o p2sqr
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ ls -l *.a
-rw-rw-r-- 1 olej olej 38216 июн 25 16:35 libsqr.a
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ ldd p2sqr
linux-vdso.so.1 (0x00007ffe299eb000)
libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f4acd67d000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f4acd52e000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f4acd513000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4acd321000)
/lib64/ld-linux-x86-64.so.2 (0x00007f4acd881000)
Выполнение:
Код: Выделить всё
olej@R420:~/2021/OWN_TEST.codes/p2sqr$ ./p2sqr
число [>0] (Enter - завершение): 7
задан аргумент: 7
эталонный результат = 2.64575
метод [0..4] (Enter - завершение методов): 3
метод: exp (...ln)
SQRT(7) = 2.64575 [-2.76652e-06%]
время 1000000 вычислений [мксек] = 53245.296
метод [0..4] (Enter - завершение методов):
число [>0] (Enter - завершение):