В этой статье представлен паттерн, который может быть использован для обеспечения динамического связывания без использования виртуальных функций для вызова перегруженных методов для объектов неоднородного контейнера при его обходе.
На правах введения
Обычно не рекомендуется прибегать к совместно используемым объектам между процессами в C++, однако это возможно. Разберемся, для чего это вообще необходимо:
- При динамическом связывании, когда виртуальная функция вызвана для объекта, компилятор не знает какая именно функция должна быть выполнена. Чтобы разрешить вызовы виртуальных функций, компилятор составляет таблицу виртуальных функций (vtable) для каждого класса, который объявляет виртуальные функции. Vtable содержит смещения (offset) для этих виртуальных функций. Когда, во время выполнения программы, создается объект класса, ему присваивается указатель на Vtable класса. Т.к. этот указатель находится в сегменте данных процесса, который создает объект, то, даже если объект будет создан в сегменте совместно используемой памяти, указатель на Vtable будет доступен только для процесса, создавшего объект. Это значит, что другие процессы, которые будут пытаться вызвать виртуальную функцию совместно используемого объекта, откажут. Это — главный повод для рассмотрения альтернатив динамическому связыванию для совместного использования объектов между процессами.
- Указатели на объекты в С++, созданные в разделяемой памяти, будут доступны в разных процессах, только если они свяжут сегмент разделяемой памяти с одним и тем же виртуальным адресом, чего ОС гарантировать не может (ОС может предложить виртуальный адрес и связать процесс с этим адресом, только если этот адрес уже не используется). Решение заключается в использовании смещений для виртуальных адресов, которые, при создании указателя на объект, прибавляются к базовому виртуальному адресу. Точно так же, вместо указателей в C++, мы должны иметь смещения.
- Компилятор выделить статические члены-данные класса в стандартном сегменте данных процесса, т.е., разные процессы будут иметь разные копии этих членов. Таким образом, если необходима одна копия этих статических членов-данных, они должны быть заменены смещениями на базовый виртуальный адрес (который отображается в разделяемую память).
- Одновременное использование разными процессами одного совместно используемого объекта, может привести к повреждению данных. Для обеспечения взаимоисключения (mutual exclusion) должен быть использован механизм IPC (inter-process communication)
«Curiously Recurring Template Pattern» (CRTP) — наиболее часто используемая альтернатива динамическому связыванию. CRTP используется для реализации статического полиморфизма. Статический полиморфизм достигает подобного эффекта благодаря виртуальным функциям, позволяя выбирать перегруженные методы в производных классах на этапе компиляции, а не во время выполнения. Используя CRTP, класс Derived наследует шаблонный класс Base, реализующий интерфейс класса Derived. Для правильного удаления экземпляров производного класса, Base наследует класс Deletor, который определяет виртуальный деструктор. Виртуальный деструктор обеспечивает объектов производных классов через указатель на базовый класс.
class Deletor
{
public:
virtual ~Deletor() {}
};
template<typename T>
class Base: public Deletor
{
public:
int Run() { return static_cast<T*>(this)->DoIt(); }
};
class Derived1: public Base<Derived1>
{
public:
int DoIt() { /* implementation for Derived1 */ }
};
class Derived2 : public Base<Derived2>
{
public:
int DoIt() { /* implementation for Derived2 */ }
};
int main()
{
Derived1 Obj1;
Derived2 Obj2;
Obj1.Run(); /* runs DoIt() implementation */
Obj2.Run(); /* runs DoIt() implementation */
};
Без использования базового класса типа Deletor, как в примере выше, производные классы не смогут храниться разнородно (например, в контейнере), поскольку каждый базовый класс CRTP — уникальный тип. Base и Base — не связанные классы, поэтом, даже при том, что эти объекты могут храниться разнородно в контейнере BaseDeletor* объектов, нельзя будет при помощи итераций обеспечить полиморфизм (вызывая, например, метод DoIt(), как в примере). CRTP подходит для приложений, где клиенты должны создавать только один тип производного класса.
Моделируемый интерфейс (паттерн проектирования)
Паттерн, представленный здесь, использует шаблоны статических функций-членов, а не шаблонные классы. Этот паттерн требует создания базового класса, определяющего шаблоны статических функций, которые будут иметь доступ к интерфейсным функциям производных классов. Цель заключается в том, чтобы можно было добавить объекты производных классов в контейнер и затем, при проходе контейнера можно было бы вызывать интерфейсные функции без необходимости что-либо знать о типе объекта.
Предположим, что нам необходима иерархия классов с динамически связанным методом int siRun(int&)
. Мы создаем базовый класс RunnableInterface
со статической шаблонной функцией Run_T
и виртуальным деструктором.
Run_T(...)
имеет тот же возвращаемый тип и те же входные параметры, что и siRun(..)
, плюс один дополнительный параметр в начале, который является указателем на объект, с которым работает статическая шаблонная функция. Таким образом, мы объявляем приватную переменную *m_pfnRun_T
, которая является указателем на функцию, конструктор, инициализирующий этот указатель нулем, шаблонную функцию (Init
), чтобы установить значение указателя на корректную реализацию статической функции (&Run_T<T>
) и открытую функцию (siRun
) для косвенного вызова статической функции через приватную переменную.
class RunnableInterface
{
private:
int (*m_pfnRun_T)(void* const, int&);
template<typename T>
static int Run_T(void* const pObj, int &k) { return static_cast<T*>(pObj)->siRun( k ); }
protected:
template<typename T>
void Init() { m_pfnRun_T = &Run_T<T>; }
public:
RunnableInterface(): m_pfnRun_T(0) {}
int siRun(int &k)
{
assert(m_pfnRun_T); // Make sure Init() was called.
return (*m_pfnRun_T)(this, k);
}
virtual ~RunnableInterface() {}
};
Заметьте, что *m_pfnRun_T
ни как не связана с шаблоном Т
. void* const pObj
используется, чтобы обеспечить создание экземпляров RunnableInterface
, независящих от типа производного класса.
Однако если производный класс, так скажем, неумышленно «подавит» определение одной из интерфейсных функций (например, siRun()
), то соответствующий static_cast
в шаблонной функции не вызовет ошибки во время компиляции и приложение, в конечном счете, упадет, когда произойдет попытка вызова неопределенной интерфейсной функции. К счастью, есть замечательный метод SFINAE (substitution failure is not an error), который может использоваться для того, чтобы во время выполнения проверить, действительно ли существует интерфейсная функция; макрос CreateMemberFunctionChecked
реализует структуру, которая разрешает определенно статическое «значение» во время компиляции, чтобы потом, во время выполнения, проверить, определена ли соответствующая интерфейсная функция для данного класса. Соответствующий макрос CheckMemberFunction
может быть вызван функцией Init()
, для определения существования интерфейсной функции во время выполнения. Параметр FNNAME
в обоих макросах должен определить имя функции; второй параметр в CheckMemberFunction
должен определить прототип функции, используя тип Т
.
// Using the SFINAE (Substitution Failure Is Not An Error) technique,
// the following macro creates a template class with typename T and a
// static boolean member "value" that is set to true if the specified
// member function exists in the T class.
// This macro was created based on information that was retrieved from
// the following URLs:
// https://groups.google.com/forum/?fromgroups#!topic/comp.lang.c++/DAq3H8Ph_1k
// http://objectmix.com/c/779528-call-member-function-only-if-exists-vc-9-a.html
#define CreateMemberFunctionChecker(FNNAME)
template<typename T>
struct has_member_##FNNAME;
template<typename R, typename C>
class has_member_##FNNAME<R C::*>
{
private:
template<R C::*>
struct helper;
template<typename T>
static char check(helper<&T::FNNAME>*);
template<typename T>
static char (& check(...))[2];
public:
static const bool value = (sizeof(check<C>(0)) == sizeof(char));
}
// This corresponding macro is used to check the existence of the
// interface function in the derived class.
#define CheckMemberFunction(FNNAME, FNPROTOTYPE)
{ assert(has_member_##FNNAME<FNPROTOTYPE>::value); }
Используя эти макросы, перепишем RunnableInterface
:
class RunnableInterface
{
private:
CreateMemberFunctionChecker(siRun);
int (*m_pfnRun_T)(void* const, int&);
template<typename T>
static int Run_T(void* const pObj, int &k ) { return static_cast<T*>(pObj)->siRun(k); }
protected:
template<typename T>
void Init()
{
CheckMemberFunction(siRun, int (T::*)(int&));
m_pfnRun_T = &Run_T<T>;
}
public:
RunnableInterface(): m_pfnRun_T(0) {}
virtual ~RunnableInterface() {}
int siRun(int &k)
{
assert(m_pfnRun_T);
return (*m_pfnRun_T)(this, k);
}
};
Теперь этот класс может быть базовым для классов, перегружающих функцию int siRun(int &k)
. Все, что мы должны делать в конструкторе производного класса — вызывать Init<Derived>();
для соединения int Derived::siRun(int &k)
производного класса со статической шаблонной функцией базового (Run_T
), как показано ниже:
class Test: public RunnableInterface
{
friend class RunnableInterface;
private:
int siRun(int &k) { k = m_value*2; return 0; }
protected:
int m_value;
public:
Test(int value): m_value(value) { RunnableInterface::Init<Test>(); }
};
class AdjustmentTest: public Test
{
friend class RunnableInterface;
private:
int siRun(int &k) { k = m_value*3; return 0; }
public:
AdjustmentTest(int value): Test(value) { RunnableInterface::Init<AdjustmentTest>(); }
};
friend class RunnableInterface;
необходимо для предоставления доступа к шаблонным функциям, которые определены в классе RunnableInterface
с сохранением модификатора доступа интерфейсных функций private
или protected
.
Теперь мы можем совершить обход по неоднородному контейнеру TestInterface*
с получением доступа к перегруженным функциям (что не может быть достигнуто с CRTP), как в примере ниже:
int main()
{
RunnableInterface* const pObj1 = new Test(1);
RunnableInterface* const pObj2 = new AdjustmentTest(4);
std::list<RunnableInterface*> list1;
list1.insert(list1.end(), pObj1);
list1.insert(list1.end(), pObj2);
std::list<RunnableInterface*>::iterator i;
for (i = list1.begin(); i != list1.end(); i++ )
{
RunnableInterface* const p = *i;
int k;
const int j = p->siRun(k);
std::cout << "RUN: " << j << ":" << k << std::endl << std::endl;
delete p;
}
return 0;
}
Но что же с переменной — указателем на функцию в классе RunnableInterface
? Все замечательно, если мы работаем в одном процессе и хотим использовать этот паттерн в каких-то целях, кроме совместно используемых объектов между процессами. Это конечно может сработать в некоторых операционных системах, если ОС присваивает одни и те же виртуальные адреса экземплярам одной и той же программы. Но, чтобы гарантировать это, мы должны использовать смещение для адреса загрузки, вместо указателя на функцию. В Windows, GetModuleHandle()
возвращает дескриптор модуля, который совпадает с адресом загрузки модуля. Тогда указатели на функцию могут быть вычислены во время выполнения для генерации адреса для вызова. Таким образом, под Windows наш класс будет выглядеть так:
class TestInterface
{
private:
template <typename T>
static int Run_T(void* const pObj, int &k) { static_cast<T*>(pObj)->siRun(k); }
typedef void (*PFN_RUN_T)(void* const, int&);
CreateMemberFunctionChecker(siRun);
// Offset to static template member functions.
unsigned long m_ulRun_T_Offset;
protected:
template <typename T>
void Init()
{
CheckMemberFunction(siRun, void (T::*)(int&));
char *pBaseOffset = (char*)GetModuleHandle(NULL);
m_ulRun_T_Offset = (unsigned long)&Run_T<T> - (unsigned long)pBaseOffset;
}
public:
TestInterface(): m_ulRun_T_Offset(0) {}
void siRun(int &k)
{
assert(m_ulRun_T_Offset); // Make sure Init() was called.
char* const pBaseOffset = (char*)GetModuleHandle(NULL);
PFN_RUN_T pfnRun_T = (PFN_RUN_T)(pBaseOffset + m_ulRun_T_Offset);
(*pfnRun_T)(this, k);
}
virtual ~TestInterface() {}
};
Результаты
Пример кода в листингах 1, 2 и 3 демонстрируют всё то, о чем говорилось выше: совместно используемые объекты между процессами.
Для разработки был использован Qt Creator 5.0.2 с MinGW 4.7 x86 и тестировалось в Windows XP и Windows 7 x64.
Файл testiface.h (листинг 1) представляет класс TestInterface, определяющий интерфейс следующих функций:
- int siRun();
- void siReset(int &k);
- void siSayHello();
ЛИСТИНГ 1 — testiface.h
#ifndef TESTIFACE_H
#define TESTIFACE_H
#include <windows.h> // for GetModuleHandle()
// Using the SFINAE (Substitution Failure Is Not An Error) technique,
// the following macro creates a template class with typename T and a
// static boolean member "value" that is set to true if the specified
// member function exists in the T class.
// This macro was created based on information that was retrieved from
// the following URLs:
// https://groups.google.com/forum/?fromgroups#!topic/comp.lang.c++/DAq3H8Ph_1k
// http://objectmix.com/c/779528-call-member-function-only-if-exists-vc-9-a.html
#define CreateMemberFunctionChecker(FNNAME)
template<typename T>
struct has_member_##FNNAME;
template<typename R, typename C>
class has_member_##FNNAME<R C::*>
{
private:
template<R C::*>
struct helper;
template<typename T>
static char check(helper<&T::FNNAME>*);
template<typename T>
static char (& check(...))[2];
public:
static const bool value = (sizeof(check<C>(0)) == sizeof(char));
}
#define CheckMemberFunction(FNNAME, FNPROTOTYPE)
{ assert(has_member_##FNNAME<FNPROTOTYPE>::value); }
typedef int (*PFN_RUN_T)(void* const);
typedef void (*PFN_RESET_T)(void* const, int&);
typedef void (*PFN_SAYHELLO_T)(void* const);
#ifndef SINGLE_PROCESS
class TestInterface
{
private:
// Implement template functions.
template <typename T>
static int Run_T(void* const pObj) { return static_cast<T*>(pObj)->siRun(); }
template <typename T>
static void Reset_T(void* const pObj, int &k) { static_cast<T*>(pObj)->siReset(k); }
template <typename T>
static void SayHello_T(void* const pObj) { static_cast<T*>(pObj)->siSayHello(); }
CreateMemberFunctionChecker(siRun);
CreateMemberFunctionChecker(siReset);
CreateMemberFunctionChecker(siSayHello);
// Offsets to static template member functions.
unsigned long m_ulRun_T_Offset, m_ulReset_T_Offset, m_ulSayHello_T_Offset;
protected:
template <typename T>
void Init()
{
CheckMemberFunction(siRun, int (T::*)());
CheckMemberFunction(siReset, void (T::*)(int&));
CheckMemberFunction(siSayHello, void (T::*)());
char *pBaseOffset = (char*)GetModuleHandle(NULL);
m_ulRun_T_Offset = (unsigned long)&Run_T<T> - (unsigned long)pBaseOffset;
m_ulReset_T_Offset = (unsigned long)&Reset_T<T> - (unsigned long)pBaseOffset;
m_ulSayHello_T_Offset = (unsigned long)&SayHello_T<T> - (unsigned long)pBaseOffset;
}
public:
TestInterface(): m_ulRun_T_Offset(0), m_ulReset_T_Offset(0), m_ulSayHello_T_Offset(0) {}
int siRun()
{
assert(m_ulRun_T_Offset); // Make sure Init() was called.
char *pBaseOffset = (char*)GetModuleHandle(NULL);
PFN_RUN_T pfnRun_T = (PFN_RUN_T)(pBaseOffset + m_ulRun_T_Offset);
return (*pfnRun_T)(this);
}
void siReset(int &k)
{
assert(m_ulReset_T_Offset); // Make sure Init() was called.
char *pBaseOffset = (char*)GetModuleHandle(NULL);
PFN_RESET_T pfnReset_T = (PFN_RESET_T)(pBaseOffset + m_ulReset_T_Offset);
(*pfnReset_T)(this, k);
}
void siSayHello()
{
assert(m_ulSayHello_T_Offset); // Make sure Init() was called.
char *pBaseOffset = (char*)GetModuleHandle(NULL);
PFN_SAYHELLO_T pfnSayHello_T = (PFN_SAYHELLO_T)(pBaseOffset + m_ulSayHello_T_Offset);
(*pfnSayHello_T)(this);
}
virtual ~TestInterface() {}
};
#else
class TestInterface
{
private:
template <typename T>
static int Run_T(void* const pObj) { return static_cast<T*>(pObj)->siRun(); }
template <typename T>
static void Reset_T(void* const pObj, int &k) { static_cast<T*>(pObj)->siReset(k); }
template <typename T>
static void SayHello_T(void* const pObj) { static_cast<T*>(pObj)->siSayHello(); }
// Pointers to static template member functions.
PFN_RUN_T m_pfnRun_T;
PFN_RESET_T m_pfnReset_T;
PFN_SAYHELLO_T m_pfnSayHello_T;
CreateMemberFunctionChecker(siRun);
CreateMemberFunctionChecker(siReset);
CreateMemberFunctionChecker(siSayHello);
protected:
template <typename T>
void Init()
{
CheckMemberFunction(siRun, int (T::*)());
CheckMemberFunction(siReset, void (T::*)(int&));
CheckMemberFunction(siSayHello, void (T::*)());
m_pfnRun_T = &Run_T<T>;
m_pfnReset_T = &Reset_T<T>;
m_pfnSayHello_T = &SayHello_T<T>;
}
public:
TestInterface(): m_pfnRun_T(0), m_pfnReset_T(0), m_pfnSayHello_T(0) {}
int siRun()
{
assert(m_pfnRun_T); // Make sure Init() was called.
return (*m_pfnRun_T)(this);
}
void siReset(int &k)
{
assert(m_pfnReset_T); // Make sure Init() was called.
(*m_pfnReset_T)(this, k);
}
void siSayHello()
{
assert(m_pfnSayHello_T); // Make sure Init() was called.
(*m_pfnSayHello_T)(this);
}
virtual ~TestInterface() {}
};
#endif // SINGLE_PROCESS
#endif // TESTIFACE_H
testclasses.h из листинга 2 демонстрирует создание трех производных классов Base
(от TestInterface
), DerivedOnce
(от Base
) и DerivedTwice
(от DerivedOnce
). В конструкторе каждого из этих классов вызывается TestInterface::Init<T>()
, где Т
— имя класса. Это все, что требуется от производных классов, чтобы заставить работать TestInterface()
. В каждом из этих классов TestInterface
объявлен дружественным для доступа к siRun, siReset, siSayHello
, с сохранением их модификаторов private/protected
.
ЛИСТИНГ 2 — testclasses.h
#ifndef TESTCLASSES_H
#define TESTCLASSES_H
#ifndef TESTIFACE_H
#include "testiface.h"
#endif
class Base: public TestInterface
{
friend class TestInterface;
private:
int siRun() { return m_value; }
void siReset(int &k) { k = m_value*10; }
void siSayHello() { std::cout << "Hello from Base" << std::endl; }
protected:
int m_value;
public:
Base(int value = 1): m_value(value) { TestInterface::Init<Base>(); }
};
class DerivedOnce: public Base
{
friend class TestInterface;
private:
int siRun() { return m_value; }
void siReset(int &k) { k = m_value*100; }
void siSayHello() { std::cout << "Hello from DerivedOnce" << std::endl; }
public:
DerivedOnce(): Base() { TestInterface::Init<DerivedOnce>(); ++m_value; }
};
class DerivedTwice: public DerivedOnce
{
friend class TestInterface;
private:
int siRun() { return m_value; }
void siReset(int &k) { k = m_value*1000; }
void siSayHello() { std::cout << "Hello from DerivedTwice" << std::endl; }
public:
DerivedTwice(): DerivedOnce() { TestInterface::Init<DerivedTwice>(); ++m_value; }
};
#endif // TESTCLASSES_H
Файл main.cpp из листинга 3 основан на WinAPI и демонстрирует:
- Создание экземпляров
Base, DerivedOnce, DerivedTwice
в разделяемой памяти процессом владельца (owner) - Доступ к объектам в разделяемой памяти процессом клиента (client)
В обоих случаях, объекты помещаются в список и в цикле к ним получается доступ как к объектам TestInterface*
, с вызовом интерфейсных функций siRun, siReset, siSayHello
. Для начала необходимо запустить программу и оставить ее работающей; процесс «OWNER» (создатель разделяемой памяти и объектов, выделенных в ней) будет первоначально идентифицирован в консоли; после этого запустите еще один экземпляр программы; «CLIENT» будет так же первоначально определен в консоли и вы должны увидеть тот же вывод (за исключением виртуального адреса, который м.б. другим), но здесь мы просто получаем доступ к объектам, которые находятся в разделяемой памяти вызывая один и тот же код из разных процессов.
Попытайтесь закомментировать одну из интерфейсных функций, например DerivedTwice::siSayHello()
, затем снова повторите запуск программы. Попытка запуска приведет к ошибке (из-за вызова CheckMemberFunction(siSayHello, void (T::*)()) в TestInterface::Init<T>()
):
Assertion failed!
Expression: has_member_siSayHello<void (T::*)()>::value
Попробуйте изменить определение siSayHello
в DerivedTwice
, добавив какой-нибудь параметр (например, double d
), затем попытайтесь скомпилировать программу. При компиляции возникнет ошибка (из-за вызова TestInterface::Init<DerivedTwice>()
в конструкторе DerivedTwice
):
In instantiation of ‘static void TestInterface::SayHello_T(void*) [with T = DerivedTwide]’;
Required from ‘void TestInterface::Init [with T = DerivedTwice]
ЛИСТИНГ 3 — main.cpp
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <list>
#include <iostream>
#include <conio.h>
#include <assert.h>
#include "testclasses.h"
int main()
{
const SIZE_T BufSize = 1024;
const TCHAR szName[] = TEXT("Local\SharedMemBlockObject");
const HANDLE hMapFile =
CreateFileMapping(
INVALID_HANDLE_VALUE, // use paging file
NULL, // default security
PAGE_READWRITE, // read/write access
0, // maximum object size (high-order DWORD)
BufSize, // maximum object size (low-order DWORD)
szName ); // name of mapping object
if (hMapFile == NULL)
{
std::cout << "Could not create file mapping object (" << GetLastError() << ").n" << std::endl;
return 1;
}
const bool fFirstProcess = (GetLastError() != ERROR_ALREADY_EXISTS);
// Map the file to this process' address space.
const LPCTSTR pBuf =
(LPTSTR) MapViewOfFile(
hMapFile, // handle to map object
FILE_MAP_ALL_ACCESS, // read/write permission
0,
0,
BufSize );
if (pBuf == NULL)
{
std::cout << "Could not map view of file (" << GetLastError() << ").n" << std::endl;
CloseHandle( hMapFile );
return 1;
}
Base *pObj1;
DerivedOnce *pObj2;
DerivedTwice *pObj3;
char *pBuf1 = (char*)pBuf;
if (fFirstProcess)
{
std::cout << "OWNER PROCESS: " << std::endl;
// Create TestInterface objects in shared memory.
pObj1 = new(pBuf1)Base; // first available memory addr
pBuf1 += sizeof(Base); // skip to next available memory addr
pObj2 = new(pBuf1)DerivedOnce;
pBuf1 += sizeof(DerivedOnce); // skip to next available memory addr
pObj3 = new(pBuf1)DerivedTwice;
}
else
{
std::cout << "CLIENT PROCESS: " << std::endl;
// Access objects that are in shared memory.
pObj1 = (Base*)pBuf1; // addr where Base obj was created
pBuf1 += sizeof(Base);
pObj2 = (DerivedOnce*)pBuf1; // addr where DerivedOnce obj was created
pBuf1 += sizeof(DerivedOnce);
pObj3 = (DerivedTwice*)pBuf1; // addr where DerivedTwice obj was created
}
char szHexBuf[12];
sprintf(szHexBuf, "0x%lx", (unsigned long) pBuf);
std::cout << "pBuf: " << szHexBuf << std::endl << std::endl;
// Place TestInterface* objects in a list.
std::list<TestInterface*> list1;
list1.insert(list1.end(), pObj1);
list1.insert(list1.end(), pObj2);
list1.insert(list1.end(), pObj3);
// Let TestInterface objects greet, run and reset generically.
std::list<TestInterface*>::iterator i;
for (i = list1.begin(); i != list1.end(); i++)
{
TestInterface* const p = *i;
p->siSayHello();
std::cout << "RUN: " << p->siRun() << std::endl;
int kk;
p->siReset( kk );
std::cout << "RESET: " << kk << std::endl << std::endl;
}
std::cout << "Press any key to end program" << std::endl;
if (fFirstProcess)
std::cout << " and destroy objects in shared memory" << std::endl;
std::cout << "..." << std::endl;
while (!kbhit())
Sleep(100);
if (fFirstProcess)
{
// Objects are no longer needed, call destructors.
for (i = list1.begin(); i != list1.end(); i++)
{
TestInterface* const p = *i;
// We need to call the destructor explicitly because
// the new with placement operator was used.
// We cannot call "delete p;"
p->~TestInterface();
}
}
UnmapViewOfFile(pBuf);
CloseHandle(hMapFile);
return 0;
}
На правах заключения
Метод, представленный в этой статье, демонстрирует паттерн, который может использоваться для избавления от виртуальных функций с возможностью динамического связывания на этапе выполнения программы. Паттерн обеспечивает возможность прохода неоднородного контейнера с вызовом перегруженных функций. Уход от виртуальных функций позволяет использовать объекты через границы процесса с сохранением возможности динамического связывания.
Автор: Renzo