Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: placement new
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
ReAl
Выделено отдельной темой.
В том же разделе, в плюсах я сам начинающий :-), если что - меня поправят.

Цитата(AHTOXA @ Nov 6 2009, 22:47) *
А что это? А то может мне тоже это нравится, а я и не знаю wink.gif

----
Почитал. Пока не понял, нравится ли... Но скорее да, чем нетsmile.gif

Это такой стандартный способ сделать то, что делать нехорошо. Ну как goto - нехорошо, а break/continue/switch+case - хорошо (правда, на switch+case делается такой Duff's device, что вся структурность в гробу переворачивается, но зато без goto).

Конкретнее, placement new - это способ сделать
Код
#include "foo.h"

    unsigned char buf[sizeof(foo)];            // выделяем место под экземпляр
    foo *pfoo = reinterpret_cast<foo*>(buf);    // заводим указатель#define
    pfoo->foo(0x88);                // ( . ) конструиреум
    pfoo->action();                    // пользуемся
    pfoo->~foo();                    // удаляем
Тут сделать можно всё, кроме строки, помеченной ( . ) - ну нельзя так.
Зато можно так
Код
#include "foo.h"

    unsigned char buf[sizeof(foo)];            // выделяем место под экземпляр
    foo *pfoo = new(buf) foo(0x88);            // конструиреум и приводим указатель
    pfoo->action();                    // пользуемся
    pfoo->~foo();                    // удаляем

и эта штука и называется placement new. Она не проверяет, достаточно ли места в предоставленном буфере и возвращает указатель на начало буфера, приведенный к конструируемому типу. Короче, сплошное "фу". Но такая лапочка.
Как было написано в какой-то статье по этому делу, "если вы не знаете, что такое alignment, то и placement new лучше не пользуйтесь".

Фактически это глобальная перегрузка оператора new своим аллокатором, тип "аллокатора" void* и он ничего не делает.
В библиотеке должна быть готовая реализация перегруженного оператора, но в avr-gcc её нет. И не надо, inline она смотрится куда лучше.
Код
#include <stdlib.h> // size_t
// placement new support - сответствующий стандарту прототип
inline void* operator new(size_t size, void* ptr) throw()
{
    (void)size;
    return ptr;
}


Ну а теперь - почему мне это нравится.
Пусть у нас есть несколько классов, которые используются строго по очереди.
Если экзмепляры заводить статически - абсолютно зря жрётся место в ОЗУ.
Если динамически - надо прицепть new/delete (пусть хоть и отмапленные на malloc/free) - а это лишний код и возможные проблемы с фрагментацией памяти (хотя если new/delete в программе всё равно нужны, то почему бы и нет).
Но можно так (NOINLINE для удобства разбора функции kwa() ):
Код
class foo {
public:
    NOINLINE foo(uint8_t mask) : _mask(mask) { PORTB &= ~_mask; DDRB |= _mask; }
    NOINLINE ~foo() { PORTB |= _mask; DDRB &= ~_mask; }
    NOINLINE void action() { PORTB ^= _mask; }
private:
    uint8_t _mask;
};

class moo {
public:
    NOINLINE moo(uint8_t mask, uint8_t period)
        : _mask(mask), _period(period), _cnt(period)
        {  PORTB &= ~_mask; DDRC |= _mask; }
    
    NOINLINE ~moo() { DDRC &= ~_mask; }
    
    NOINLINE void action() {
        if( --_cnt == 0) {
            _cnt = _period;
            PORTC ^= _mask;
        }
    }
private:
    uint8_t _mask;
    uint8_t _period;
    uint8_t _cnt;
};

const int bufsize = sizeof(foo) > sizeof(moo) ? sizeof(foo) : sizeof(moo);

uint8_t buf[bufsize];

void kwa()
{
    uint8_t i;
    
    foo *pfoo = new(buf) foo(0x11);
    i = 8;
    do {        pfoo->action();        } while(--i);
    pfoo->~foo();

    moo *pmoo = new(buf) moo(0x11,3);
    i = 8;
    do {        pmoo->action();        } while(--i);
    pmoo->~moo();
}

Код kwa()
Код
.global    _Z3kwav
    .type    _Z3kwav, @function
_Z3kwav:
    push r17
; operator new(size_t,void*) заинлайнился в ничто
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    ldi r22,lo8(17)
    rcall _ZN3fooC1Eh; конструктор foo
    ldi r17,lo8(8)
.L20:
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    rcall _ZN3foo6actionEv; попользовались
    subi r17,lo8(-(-1))
    brne .L20
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    rcall _ZN3fooD1Ev; деструктор foo
    
; new опять пропал
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    ldi r22,lo8(17)
    ldi r20,lo8(3)
    rcall _ZN3mooC1Ehh; конструктор moo
    ldi r17,lo8(8)
.L21:
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    rcall _ZN3moo6actionEv; попользовались
    subi r17,lo8(-(-1))
    brne .L21
    ldi r24,lo8(buf)
    ldi r25,hi8(buf)
    rcall _ZN3mooD1Ev; деструктор moo

    pop r17
    ret
Массив для экземпляров классов можно и на стеке завести, что лучше - зависит от условий. Даже alloca() может быть лучше malloc/free
К сожалению, нельзя сказать delete(buf) pfoo - компилятор путается, нужно вручную звать деструктор (ну а удалять ничего и не нужно)
Если деструкторы по задаче классу не нужны, лучше не вызывать и не заводить даже пустые.

Ну так пока всё.
AHTOXA
Спасбо, очень познавательно.
Видится красивый способ запуска подзадач в scmRTOS:

Код
char thread_buf[MAX_THREAD_SIZE];

template <>
OS_PROCESS void TProc1::Exec()
{
    char ch;
    for(;;)
    {
        Tasks.pop(ch); // достаём задачу из очереди
        switch(ch)
       {
       case 'A':
            thread_A_t * thread = new(thread_buf) thread_A_t;
            thread->work();
            thread->~thread_A_t();
             break;
        case 'B':
             thread_B_t * thread = new(thread_buf) thread_B_t;
             thread->work();
             thread->~thread_B_t();
              break;
         default:
             thread_err_t * thread = new(thread_buf) thread_err_t;
             thread->work();
             thread->~thread_err_t();
              break;
         }
    }
}


Неперекрываемость во времени гарантирована, память для подзадач распределена статически. Красотаsmile.gif
ReAl
Цитата(AHTOXA @ Nov 7 2009, 12:21) *
Видится красивый способ запуска подзадач в scmRTOS:
Неперекрываемость во времени гарантирована, память для подзадач распределена статически. Красотаsmile.gif

Да-да-да!!!
Собственно, я сейчас в одной работе мучаю на mega168 на плюсах нечто prothothread-подобное (там довольно много ОЗУ для всяких буферов нужно, вытеснялка не лезет нивкакую со своими потребностями в стеке). Тихий ужас с непривычки :-)

Так там, в силу необходимости переключений/ожиданий на верхнем уровне задачи, очень удобно для разных дел с ожиданиями "вызывать" подзадачи, реализующие нужный функционал и ждать их завершения. Выходит много мелких "задач", каждая из которых жрёт место в ОЗУ под свои приватные-"локальные" переменные. Ну вот вспомнил про placement new, полегчало.

Правда, VMT задач один чёрт в ОЗУ хранится :-(
Им бы во флеше жить, константные ж вроде, никаких поводов меняться им нет.


Цитата(ReAl @ Nov 7 2009, 11:36) *
К сожалению, нельзя сказать delete(buf) pfoo - компилятор путается, нужно вручную звать деструктор (ну а удалять ничего и не нужно)
"историческая справка"
Синтаксиса для placement delete нет, я так понимаю, что для delete(buf) pfoo; просто само собой при разборе выйдет, что делается попытка удалить сам буфер buf - по разруливанию прототипов подходит delete(unsigned char*), а pfoo после закрывающей скобки вообще syntax error. Именно поэтому не выйдет просто через delete вызвать деструктор и деаллокатор.
В нашем случае деаллокатор и не нужен, а для случая действительно отдельного менеджера памяти рекомендуют вызывать оператор вручную.
Код
class my_allocator; //ну вот как-то там выделяет
extern my_allocator ma;

// эти две должны пользоваться функциями my_allocator для выделения-освобождения
void* operator new(size_t size, my_allocator& a) throw();
void operator delete(void*, my_allocator& a) throw();

void kwa()
{
    foo *pfoo = new(ma) foo(0x11);
    pfoo->action();
    pfoo->~foo();
    operator delete(pfoo, ma);
}
dxp
Цитата(ReAl @ Nov 7 2009, 15:36) *
Массив для экземпляров классов можно и на стеке завести, что лучше - зависит от условий.

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

Цитата(AHTOXA @ Nov 7 2009, 16:21) *
Спасбо, очень познавательно.
Видится красивый способ запуска подзадач в scmRTOS:



Чем это в данном конкретном случае отличается от:

Код
char thread_buf[MAX_THREAD_SIZE];

template <>
OS_PROCESS void TProc1::Exec()
{
    char ch;
    for(;;)
    {
        Tasks.pop(ch); // достаём задачу из очереди
        switch(ch)
       {
       case 'A':
            thread_A_t * thread;
            thread->work();
             break;
        case 'B':
             thread_B_t * thread;
             thread->work();
              break;
         default:
             thread_err_t * thread;
             thread->work();
              break;
         }
    }
}


?

Объект локальный, время жизни тоже локальное. Зачем аллокация?

Мне представляется, что более логичным решением для ситуации где требуется создавать объекты с не локальным и не статическим временами жизни было бы написание своего простого менеджера памяти на основе очереди указателей и набора пулов, отличающихся размерами блоков (чтобы избежать фрагментации). Размеры задавать на этапе компиляции статически. Код будет простым, эффективным, сам по себе подход стандартный - просто перегрузить операторы new/delete своими. Давно собирался такое соорудить, но пока жизнь по настоящему не приперла. smile.gif
AHTOXA
Цитата(dxp @ Nov 7 2009, 18:51) *
Чем это в данном конкретном случае отличается от:


Наверное всё же так:
Код
template <>
OS_PROCESS void TProc1::Exec()
{
     char ch;
     for(;;)
     {
         Tasks.pop(ch); // достаём задачу из очереди
         switch(ch)
        {
        case 'A':
             thread_A_t thread;
             thread.work();
            break;
...
         }
     }
}

?
Тоже вариант, сейчас так и делаюsmile.gif. Но тут все объекты отъедают стек процесса. А в случае с placement new у них свой статически распределённый буфер thread_buf[]. В чём-то это может быть удобнее.
ReAl
Цитата(dxp @ Nov 7 2009, 15:51) *
Я, может, чего-то не понял, но на стеке (т.е. по сути локальной переменной), имхо, смыла нет. На стеке можно просто заводить локальные объекты без всяких дополнительних телодвижений.
avr-gcc тут
Код
void kwa_kwa()
{
    uint8_t i;
    {    
        foo f(0x11);
        i = 8;
        do {
            f.action();
        } while(--i);
    }
    {    
        moo m(0x22,3);
        i = 8;
        do {
            m.action();
        } while(--i);
    }
}
резервирует на стеке суммарный объём, а не размер наибольшего класса. А union сделать нельзя.

Цитата(dxp @ Nov 7 2009, 15:51) *
Мне представляется, что более логичным решением для ситуации где требуется создавать объекты с не локальным и не статическим временами жизни было бы написание своего простого менеджера памяти на основе очереди указателей и набора пулов, отличающихся размерами блоков (чтобы избежать фрагментации).
Да, мне нужно нелокальное (но можно статически выделить буфер для них всех). Но ради одного, максимум двух "слоёв" эдаких "оверлеев" не захотелось морочиться даже с простым менеджером.
AHTOXA
Цитата(ReAl @ Nov 7 2009, 15:57) *
Правда, VMT задач один чёрт в ОЗУ хранится :-(


arm-gcc в этом смысле получше, VMT хранит во флеше.

Цитата(ReAl @ Nov 7 2009, 19:11) *
avr-gcc тут резервирует на стеке суммарный объём, а не размер наибольшего класса. А union сделать нельзя.


А вот тут - то же самое, суммарный объём. Непорядокsmile.gif
dxp
Цитата(ReAl @ Nov 7 2009, 20:11) *
avr-gcc тут
...
резервирует на стеке суммарный объём, а не размер наибольшего класса.

Это он неправильно делает. smile.gif Ну, т.е. по сути этот прием есть ручная оптимизация.
XVR
Кстати, вместо placement delete (которого нет) можно сделать обычный delete в самом классе (пустой). Тогда можно будет писать delete my_object вместо явного вызова деструктора
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.