Сеанс низкоуровневой оптимизации использования памяти в программах на языке С++ с полным разоблачением

«В вычислительной технике оптимизацией называется процесс модификации системы для улучшения её эффективности. Система может быть одиночной компьютерной программой, набором компьютеров или даже целой сетью, такой как Internet.

Несмотря на то, что слово „оптимизация“ использует тот же самый корень, что и слово „оптимальный“, процесс оптимизации весьма редко порождает оптимальную систему, поэтому всегда имеются уступки (tradeoffs).

Оптимизация должна проводиться с осторожностью. Тони Хоар впервые произнёс, и Дональд Кнут впоследствии часто повторял известное высказывание: „Преждевременная оптимизация — это корень всех бед“. Очень важно иметь для начала озвученный алгоритм и работающий прототип.»
© wiki

В этой статье мы попытаемся заставить наши алгоритмы работать быстрее, используя методики низкоуровневой оптимизации выделения памяти на С++. Нужно понимать, что все описанные в статье методики должны применятся с крайней осторожностью и в исключительных случаях, поскольку за внедрение любых элементов низкоуровневой оптимизации часто приходится платить гибкостью, переносимостью, понятностью или масштабируемостью приложений. Но если у вас как раз тот случай, когда отступать некуда, тогда — добро пожаловать.

1. Оптимизация «Конкретных Фабрик»

Я думаю, многие неоднократно сталкивались с классическим паттерном «Фабрика», или «Конкретная Фабрика», в терминологии GoF:

<code style="display:block!important;">struct IObject
{
     virtual ~IObject(){}
     virtual void DoIt()=0;
};
class CFactory
{
public:
void CreateSmth(int objType, std::auto_ptr<IObject> * pResult)
    {
        if (iValue<0)
        {
            pResult->reset(new CObject1);
        }
        else
        {
            pResult->reset(new CObject2);
        }
    }
};</code>

Паттерн исключительно хорош для избавления от бесконечных if’ов и switch’ей по всему коду, но имеет одну неприятную особенность, связанную с чрезмерным использованием динамической памяти, что иногда неприятно отражается на производительности С++ программ. Мы постараемся отучить его от этого, при определенных оговорках, естественно.

Посмотрим еще раз на процесс использования фабрики:

ДействиеСмысл
std::auto_ptr<IObject> product;мы объявляем некоторый контейнер для продукции, место, где будет «храниться» созданный объект
MySuperFactory.CreateSmth(1, &product);создаем объект в этом контейнере с помощью фабрики
product->DoIt();используем полученный объект через известный интерфейс
mySuperContainer.Add( product );или передаем владение контейнером кому-нибудь еще

Для оптимизации описанного жизненного цикла продукции можно применить следующие утверждения:

1) Специальная форма оператора new — placement new позволяет создавать объекты в произвольном, «сыром» буфере. Например, так:

<code style="display:block!important;">char buffer[1024];
new(buffer)CObject(a,b,c);</code>

Очень полезная вещь, учитывая, что мы можем выделить буфер более эффективно, чем стандартная реализация new. Правда, использование placement new тоже вносит некоторые трудности в жизнь программиста:

a) Сырой буфер должен быть выровнен на границу, зависимую от платформы;
b) Деструктор созданного объекта должен быть вызван вручную.

2) Использование стека является удачной альтернативой хипу. Было бы намного более заманчиво использовать в качестве контейнера буфер на стеке, т.е. использовать

<code style="display:block!important;">MyWrapperAroundLocalBuffer<ObjectSize, IObject> product;</code>

вместо оригинального

<code style="display:block!important;">std::auto_ptr<IObject> product;</code>

Основная проблема создания контейнера на стеке заключается в том, что в стандартном С++ невозможно выделить на стеке объект произвольного размера (неизвестного на этапе компиляции).

3) Но, поскольку наша фабрика — конкретная (а не абстрактная), и мы знаем обо всех типах ее продукции, то мы определенно сможем узнать максимальный размер объекта-продукции на этапе компиляции. Например, используя списки типов:

<code style="display:block!important;">//--------------------------------
// typelist basics
//--------------------------------
template<class Head, class Tail>
struct Node
{
};
struct NullNode
{
};</code>

Мы можем написать рекурсивную compile-time функцию для вычисления максимального размера объекта, из числа типов, находящихся в списке:

<code style="display:block!important;">template <class List>
struct GetMaxSize
{
};
template <class Head, class Tail>
struct GetMaxSize<Node<Head, Tail> >
{
    static const size_t TailSize = GetMaxSize<Tail>::Result;
    static const size_t Result = (TailSize > sizeof(Head) ) ? TailSize : sizeof(Head);
};
template <>
struct GetMaxSize<NullNode>
{
    static const size_t Result = 0;
};</code>

После чего мы можем создать список из всех возможных типов продукции для фабрики CFactory:

<code style="display:block!important;">   typedef utils::Node<CObject1>, 
                 utils::Node<CObject2>, 
                      utils::NullNode
                     > 
                > ObjectList;
   static const size_t <strong>MaxObjectSize</strong> = utils::GetMaxSize<ObjectList>::Result;</code>

Как результат — теперь мы на 100% уверены, что любой произведенный объект наверняка поместится в буфере размера MaxObjectSize (если мы правильно написали список типов, конечно):

<code style="display:block!important;">   char buffer[ MaxObjectSize ];</code>

Этот буфер можно легко выделить в стеке.

4) Поскольку наш контейнер должен уметь хранить в себе объекты различных типов, мы вправе потребовать от них помощи в виде поддержки соответствующего интерфейса:

<code style="display:block!important;">struct IManageable
{
    virtual ~IManageable(){}
    virtual void DestroyObject(void * pObject)=0;
    virtual void CreateAndSwap(void * pObject, int iMaxSize)=0;
    virtual void CreateAndCopy(void * pObject, int iMaxSize)=0;
};</code>

Т.е. объект, который хочет жить в нашем контейнере, должен уметь:

a) удалять объекты своего типа по определенному адресу;

b) использовать технологию Create And Swap для передачи владения (опционально);

c) использовать технологию Create And Copy для создания копии объекта (опционально).

Структуру контейнера можно схематично представить следующим образом:

Т.е. наш контейнер содержит в себе сырой буфер, и два указателя на различные виртуальные базы объекта, расположенного в сыром буфере:

a) Указатель на IManageable для управления жизненным циклом объекта;

b) Указатель на пользовательский интерфейс IObject, методы которого, собственно, пользователь фабрики и хотел бы вызывать.

Поскольку достаточно трудоёмко добавлять для каждого класса продукции поддержку интерфейса IManageable, имеет смысл написать шаблон manageable, который будет делать это автоматически за нас:

<code style="display:block!important;">// manageable control flags (iFlags field)
const int allow_std_swap = 1; 
const int allow_copy     = 2;
const int allow_all      = 3;


// class manageable: wrapper, provides binary interface to manage object's life
template<class ImplType, int iFlags>
class manageable:public IManageable, public ImplType
{
    typedef manageable<ImplType, iFlags> ThisType;

    virtual void DestroyObject(void * pObject)
    {
        ((ThisType*)pObject)->~ThisType();
    }
    // CreateAndSwap
    template<int iFlags>
    void CreateAndSwapImpl(void * /*pObject*/, int /*iMaxSize*/)
    {
        throw std::runtime_error("Swap method is not supported");
    }

    template<>
    void CreateAndSwapImpl<allow_std_swap>(void * pObject, int /*iMaxSize*/)
    {
        ThisType * pNewObject = new(pObject)ThisType();
        pNewObject->swap(*this);
    }

    virtual void CreateAndSwap(void * pObject, int iMaxSize)
    {
        if (sizeof(ThisType)>iMaxSize)
            throw std::runtime_error("Object too large: swap method failed");
        CreateAndSwapImpl<iFlags & allow_std_swap>(pObject, iMaxSize);
    }

    // CreateAndCopy
    template<int iFlags>
    void CreateAndCopyImpl(void * /*pObject*/, int /*iMaxSize*/)
    {
        throw std::runtime_error("Copy method is not supported");
    }
    template<>
    void CreateAndCopyImpl<allow_copy>(void * pObject, int /*iMaxSize*/)
    {
        new(pObject)ThisType(*this);
    }

    virtual void CreateAndCopy(void * pObject, int iMaxSize)
    {
        if (sizeof(ThisType)>iMaxSize)
            throw std::runtime_error("Object too large: copy method failed");
        CreateAndCopyImpl<iFlags & allow_copy>(pObject, iMaxSize);
    }

public:
    manageable()
    {
    }
    template<class Type0>
    manageable(Type0 param0)
        : ImplType(param0)
    {
    }
};</code>

Шаблон параметризируется типом объекта и флагами, указывающими, какие методы нужно поддерживать. Например, если указать флаг allow_copy, компилятор будет требовать от объекта наличия конструктора копирования для реализации метода CreateAndCopy; аналогично, если указать флаг allow_swap, будет сгенерирована функция CreateAndSwap на основе метода объекта swap, который вам необходимо будет написать самому.

Итак, наша оптимизированная фабрика приобретет следующий вид:

<code style="display:block!important;">class CFastConcreteFactory
{
public:
    typedef utils::Node<utils::manageable<CObject1, utils::allow_copy>, 
                 utils::Node<utils::manageable<CObject2, utils::allow_copy>, 
                      utils::NullNode
                     > 
                > ObjectList;

    static const size_t MaxObjectSize = utils::GetMaxSize<ObjectList>::Result;
    typedef utils::CFastObject<MaxObjectSize, IObject> AnyObject;

    void CreateSmth(int iValue, AnyObject * pResult)
    {
        if (iValue<0)
        {
            pResult->CreateByCopy(utils::manageable<CObject1, utils::allow_copy>());
        }
        else
        {
            pResult->CreateByCopy(utils::manageable<CObject2, utils::allow_copy>());
        }
    }
};</code>

И использовать ее будет так же просто, как и обыкновенную:

<code style="display:block!important;">// usage sample
CFastConcreteFactory factory;
CFastConcreteFactory::AnyObject product;
factory.CreateSmth(-1, &product);
product->DoIt();
// copy sample
CFastConcreteFactory::AnyObject another_product;
product.Copy( &another_product );</code>

Но выполняться сие творение будет значительно быстрее (см. src\win32_fast_object_sample).

Весь исходный код, примеры, performance и unit tests вы можете найти в библиотеке MakeItFaster: src\make_it_faster\.

2. Оптимизация «Ареной»

Второй метод оптимизации, предлагаемый к рассмотрению, значительно проще, однако, область его применения несколько меньше, да и отстрелить им себе ногу значительно легче.

Представим себе, что у нас есть некоторый итерационный алгоритм, повторяющий некоторое действие N раз:

<code style="display:block!important;">int mega_algorithm(int N)
{
   int Count =0;
   for(int i =0 ; i<N; ++i)
   {  
      Count += DoSmth1( );
   }
   return Count;
}</code>

Предположим далее, что в отношении данного алгоритма справедливы два условия:

1) алгоритм не имеет побочных эффектов;

2) мы можем предсказать максимальный размер динамической памяти, выделяемой на итерацию (назовем его MaxHeapUsage).

В таком случае мы можем воспользоваться паттерном «Арена» для его оптимизации. Суть паттерна довольна проста. Для его реализации мы:

a) подменяем стандартные new/delete своими;

b) перед стартом алгоритма регистрируем некоторый буфер-арену pBuffer размером MaxHeapUsage, и устанавливаем индекс, указывающий на начало свободного места FreeIndex в 0;

c) в обработчиках new выделяем память непосредственно в буфере, сдвигая FreeIndex на величину аллокации. Возвращаем естественно ((char *) pBuffer + oldFreeIndex);

d) после каждой итерации алгоритма устанавливаем FreeIndex в 0, очищая таким образом всю память, которую итерация выделила для своих нужд;

e) после конца алгоритма дерегистрируем наш буфер-арену.

Очень просто и эффективно. Это довольно опасный паттерн, потому что в production code довольно сложно гарантировать условие (1) — но хорошо подходит для около-вычислительных задач (например, из области разработки игр).

При использовании STL контейнеров, конкретный экземпляр арены можно привязать к объекту контейнера так, что объявление и использование контейнера практически не будет отличаться от оригинала, например:

<code style="display:block!important;">    typedef utils::arena_custom_map<int,int, utils::CGrowingArena<1024> >::result Map1_type;
    Map1_type map1;

    for(int i = 0;i<iNumberOfElements;++i)
    {
       map1[i] = i+1;
    }</code>

В данном примере вся память для объекта map1 выделяется из расширяемого буфера CGrowingArena, выделенная память удалится в деструкторе при уничтожении объекта.

Весь исходный код, примеры, performance и unit tests вы можете найти в библиотеке MakeItFaster.

Архив с кодом можно скачать здесь: apriorit.com/...​wnloads/arena-factory.rar

Материал подготовили Виктор Милокум и компания ApriorIT.

Все про українське ІТ в телеграмі — підписуйтеся на канал DOU

👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn



48 коментарів

Підписатись на коментаріВідписатись від коментарів Коментарі можуть залишати тільки користувачі з підтвердженими акаунтами.

Я не сомневаюсь, что проделана отличная работа, однако, материал труден для восприятия. Не видно где главное, а где второстепенное и что в результате получилось. И как это можно повторно использовать. У большинства людей не хватает времени, чтобы прочесть всё строчка-за-строчкой и понять, где соль.
На мой взгляд проект на github с небольшим readme вроде как facebook/folly позволил бы открыть метод для open source сообщества и можно было бы пообщаться более содержательно а также «отшлифовать» код совместными усилиями, чтобы подошло для разных проектов.
Начал читать и сразу много вопросов. Не осилю, потому как некоторые вещи бы делал с самого начала не так — хотелось бы увидеть как оно всё в целом, трудно без целостной картины.

Лучше бы параллельный код писать научились. А то выжимаете с кода наносекунды, но использует только одно ядро 8-ядерника.

Ну так одно другому не мешает же

«В топку».

Якщо об’єкт тимчасовий — нічого його виділяти-звільняти у динамічній пам’яті. Якщо ж об’єкт потрібен на постійно, то ваші оптимізації відразу йдуть куда подалі.

«Тимчасовий» — это тоже не всегда гуд, если он тимчасовий в локальной области фидимости, (метод какой то) и этот метод вызывается по 100 раз в секунду, то его постоянное создание /удаление — все таки напряжно для производительности. Тогда есть смысл в оптимизации, например вынести его на один уровень видимости (сделать члено класса например, если нужно — статическим) и управлять его «временем жизни»...

Наскільки мені вдалось зрозуміти, в статті йдеться про мову C++. Так от, в мові C++ методи не створюються і не звільняються. Методи, це — код, який генерується під час компіляції і лежить в сегменті коду програми. Про що ви говорите?

Немножко не так вы меня поняли.

например метод класса AA

//------------ где то там объявление class AA --------

void AA::incr()
{
std::string localStr("abc", 4096);
// что то делаем с localStr
--------------------------------------

}

В данном случае localStr будет создаваться в стеке, каждый раз при вызове метода AA::incr(), если таких вызво будет много, то получается довольно накладно, выход — сделать localStr членом класса. Тут мы как раз и расширяем область видимости localStr, хотя нигде , кроме метода AA::incr() он (localStr) и не используется.

Не можна порушувати принципи ООП заради оптимізації. Треба корегувати дизайн ООП, щоб він позволяв створювати оптимізовану програму.
В даному конкретному надуманому випадку, якщо ви стрічку використовуєте лиш як буфер, то і зробіть її як буфер на стеку. Якщо стеку шкода — зробіть її як динамічно виділений буфер в TLS. Якщо можлива рекурсія — зробіть масив/список буферів у TLS. Або зробіть глобальний кеш буферів на всі потоки із засобами синхронізації.

А якщо стрічка міняється по типу insert/erase то такий, значить, ви фуйовий оптимізатор і від перевиділень пам’яті вам нікуди не подітися. Тому, взагалі, не заморочуйтесь дурницями і пишіть так, як вам під силу.

Не можна порушувати принципи ООП заради оптимізації. Треба корегувати дизайн ООП, щоб він позволяв створювати оптимізовану програму.

Вот тут мы и подходим к ключевому моменту разработки...
На этапе проектирования приложения -
1 заложить соблюдения всех принципов ООП,
2 использования шаблонов проектирования,

3 поддержу всех правил проектирования и структурирования приложения.

4 и при этом оптимизировать программу по скорости работы, используя инструменты языка,

Вот как раз совместить 1,2,3 с 4 пунктом — это и есть высший пилотаж для разработчика/архитектора приложения. и в идеале такое мало где встречается.

При побудові початкової архітектури проектується лиш груба об’єктна модель верхнього рівня. Детально продумати все до найдрібніших деталей — це не в людських силах і це непотрібно. Оптимізація і деталізація об’єктної можелі на нижніх рівнях — це вже задача, яка вирішується на рівні розробки і при правильному підході на високорівневу картину ніяк не впливає. Просто робити це треба нормально, а не так, як описано у статті.

Статья должна называться — как исправить то, что осталось от С++ после испоганивания языка стандартизаторами. :) В 90е такой фигни не было.

Основная проблема создания контейнера на стеке заключается в том, что в стандартном С++ невозможно выделить на стеке объект произвольного размера (неизвестного на этапе компиляции).

Это не совсем корректно. В C99 есть такая штука как variable-length arrays — которая именно это и позволяет (к сожелению не все компиляторы поддерживают C99, в том числе оч. популярный от MS). Также не стоит забывать о существовании функции alloca, которая выделяет память именно из стэка. Конечно, всё зависит от вашей платформы компилятора и доступных библиотек.

По делу, имхо, перемудрили, похоже что решали эту задачу именно таким способом из спортивного интереса.


в стандартном С++ невозможно выделить на стеке объект произвольного размера (неизвестного на этапе компиляции).

В C99 есть такая штука как variable-length arrays — которая именно это и позволяет

prooflink?

Похоже поиск не в моде :)

en.wikipedia.org/wiki/C99
смотрим в раздел Design

en.wikipedia.org/...le-length_array

Но лучше конечно в стандарт заглянуть, потому как эта фича меняет ряд особенностей языка к которым все давно привыкли.

Думается что топикстартеру это не поможет т.к. в ц++ вроде такой фичи нету.

Код занятный, однако оптимизация весьма спорная. А можно узнать к какой ситуации, если не секрет, относилось «если отступать некуда»? :) Потому как я себе с трудом представляю области, кроме embedded, где такой финт ушами мог бы принести пользу не принося бессоных ночей дебаггинга после деплоймента.

я тоже таким когда-то болел, особенно начитавшись майерсов с александресками :) нормальный, в общем, велик, но может лучше попробывать какой-нибудь boost pool? Или вот такое мнение в виде доклада на ACM было «Reconsidering custom memory allocation» (dl.acm.org/....cfm?id=582421

олсо auto_ptr уже таво, deprecated ;)

Недавно тестировал custom allocator, слил по скорости C++ new. Может под виндой malloc медленный? glibc использует модифицированную версию Doug Lea malloc, а там хорошие оптимизации используются и за 4 байтами никто контексты в ядро ОС не щелкает.

кстати да, работа с памятью в рамках стандартных new и malloc ведь очень сильно будет различаться в зависимости от пары компилятор/целевая платформа и что для msvc/win32 будет хорошо, для какого-нибудь gcc/aix смерть

и все же C++ - черная магия! :)

Черная она в индии, а у нас она исключительно белая ;)

Оффтопик:
Ну слава богу, на ДОУ начали постить технические статьи, а то от силиконовых хакеров и профитных фаундеров уже на зубах приторно.
Конкретно по теме:
В рамках какой задачи возникла необходимость оптимизировать размещение объектов таким образом? Т. е., что это за объекты такие, что затраты на их размещение становятся сравнимыми с выполняемой полезной работой и существенно влияют на общую производительность приложения?

на моем опыте разработки HTML парсера, максимально возможный уход от динамического выделения памяти (вариация на тему «арены») дала прирост по быстродействию в 2 раза. Каждый вызов malloc обходится не дешево.

Народ для таких целей пишет свои аллокаторы (что бы частое выделение-высвобождение не приводило к фрагментации памяти). Ну на крайняк можно просто использовать заранее выделенный буфер памяти, это если работать с выделяемыми блоками памяти статического размера.

Ну на крайняк можно просто использовать заранее выделенный буфер памяти, это если работать с выделяемыми блоками памяти статического размера.

именно так и делали. Прикидывали, сколько может быть объектов исходя из размера файла, и в редких случаях, удваивали эту память, если не влазили.

Константин, а зачем Вы разрабатываете еще один html парсер, если не секрет ?

Если никто не против то я отвечю вам за Константина, потому как сам писал пару лет назал HTML парсер на С++ и активно его счас юзаю в проекте.

Существует множество уже реализованых html парсеров, написаных на том же С++, но если у вас узкоспецифическая задача ХТМЛ парсинга(допустим маркирование буфера с ХТМЛ контентом по присутствию некоторых тэгов-их аттрибутов, то всей мощи этих парсеров вам и не нужно... зато нужна максимальная производительность) то возможно есть смысл написать что то быстро работающее — узкоспецифическое... то есть маленький костыль ...

свой парсер ближе к телу :)

Наверное специализированный гугл пишут.

yet another HTML parser я писал в 2008 году для поискового движка meta.ua

после просмотра нескольких open source HTML парсеров (С/С++) было обнаружено, что они не отвечают выдвигаемым требованиям. Подпливание имеющихся парсеров под наши нужды, было оценено как менее перспективное, чем писать собственный парсер. Главным требованием была поддержка non-well-formed HTML в таком же виде, как это делал IE6 (под который исторически написано пол интернета)


void CreateSmth(int objType, std::auto_ptr<IObject> * pResult)

первый раз в жизни вижу указатель на auto_ptr. лучше бы его просто по значению вернуть, как результат функции.

В общем да, это больше в демонстрационных целях сделано, чтобы показать близость оригинала:

void CreateSmth(int iValue, std::auto_ptr<T> * pResult)

к переделанному варианту с fast pimpl’ом:

void CreateSmth(int iValue, AnyObject * pResult)

ЗЫ: хотя раньше такой код можно было встретить значительно чаще, можно убедиться с помощью comeau online, который поддерживает std::auto_ptr в качестве возвращаемого значения начиная с версии 4.2.45.2. Ибо rvalue references появились не сразу.

Идеи правильные но имплементация overengineered.

Код местами поелся, особенно при написании template. Будьте добры поправить пост. Нужно заменить < и > на html-эквиваленты.

Тысяча чертей, на ДОУ для камментов нужен Markdown!

Да вроде не все. Местами не хватает открывающих угловых скобочек, да и где-то с двоеточием опечатка вышла.

Поддерживаю предыдущего оратора. Код — это Ъ и Ы.

А по делу, блин, как же С+±ники это читают — то? Я в шоке.

Посему, если вас не затруднит, не могли бы вы пояснить, что обозначает вот эта конструкция у вас: void CreateAndSwapImpl(void * /*pObject*/, int /*iMaxSize*/) ?

Я правильно понимаю, что компилятор в данном случае просто и жизнерадостно приравняет коммент к тому, что на входе нет переменных и потом, при вызове функции без переменных просто будет выполняцца функция, выдающая ошибку?

Где вы используете вот эти константы, ибо не вижу свитча в коде или чего — то, к чему можно применить:
const int allow_std_swap = 1;
const int allow_copy = 2;

const int allow_all = 3; ???

З.Ы. Сорри, за дурные вопросы, но я просто не могу понять, что написано в первом примере.
З.З.Ы. ЛИнк на побочные эффекты неверный. Правильный — ru.wikipedia.org/...ограммирование

З.З.З.Ы. docx — это нифига не Ъ и Ы.

В теле метода параметры не используются, а просто бросается ансаппортед. Чтобы избежать генерации warning`а, названия параметров не передаются. Комментарии — для удобства чтения, не более.

при вызове функции без переменных просто будет выполняцца функция, выдающая ошибку?

Нет, такому методу все равно нужно передавать параметры согласно сигнатуре.

Угу. Пасиб. Полистал сам код из архива. Парсер по-моему посжирал некоторые куски кода из поста.

Кажется все съеденные куски вернул.

В С++ необязательно писать имена переменных в параметрах функции, главное определить тип передаваемых пареметров -
void helloWord(int /*no name*/) {std::cout << «hello Word»;}
Многои товарищи не пишут названия параметров в объявлении методов/ф-ций , а пишут их только в определении -
void helloWord(int /*no name*/);

void helloWord(int num) {std::cout << «hello Word NUM-» << num; }

Спасиб. Я уже понял по описанию ниже, что это для того, чтобы саппресснуть ворнинг какой-то.
Читаемость кода не очень. Отвык я от этого дела как-то.

Шаблон manageable принимает набор из этих флагов вторым параметром для того, чтобы выбрать подходящиую стратегию для копирования и обмена объектов (CreateAndSwapImpl или CreateAndCopyImpl).
Например тут:
virtual void CreateAndCopy(void * pObject, int iMaxSize)
{
...
CreateAndCopyImpl<iFlags & allow_copy>(pObject, iMaxSize);
}
в зависимости от переданного значения флага на этапе компиляции выберется либо специализация для allow_copy:
// CreateAndCopy
template<>
void CreateAndCopyImpl<allow_copy>(void * pObject, int /*iMaxSize*/)
{
new(pObject)ThisType(*this);

}

использующая конструктор копирования, либо инстанциируется сам шаблонный метод:
template<int iFlags>
void CreateAndCopyImpl(void * /*pObject*/, int /*iMaxSize*/)
{
throw std::runtime_error("Copy method is not supported");

}

выкидывающий исключения. Если целевой тип умеет копироваться, имеет смысл создать враппер так: manageable<Type, allow_copy>. Тогда у него будут доступен соответствующий метод Copy на базе конструктора копирования класса Type. Можно будет его использовать в CreateByCopy, например.
Если Type не умеет копироваться, но имеет метод void swap(Type &) к примеру, то можно создать враппер как manageable<Type, allow_swap> и тогда полученному fast object’у можно будет делать Move.

И так далее, если у целевого типа есть какие-то другие нестандартные методы с семантикой копирования и деструктивного копирования, то достаточно легко добавить свои флаги и реализацию для них.

Спасиб. Понял. У вас парсер прост сожрал часть тегов в шаблонах.

разве можно такое в конце дня постить!? =)

надеюсь, будет не только интересно, но и полезно прочесть...

Підписатись на коментарі