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

 
 
> placement new, стандартный способ сделать то, что делать нехорошо
ReAl
сообщение Nov 7 2009, 09:36
Сообщение #1


Нечётный пользователь.
******

Группа: Свой
Сообщений: 2 033
Регистрация: 26-05-05
Из: Бровари, Україна
Пользователь №: 5 417



Выделено отдельной темой.
В том же разделе, в плюсах я сам начинающий :-), если что - меня поправят.

Цитата(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 - компилятор путается, нужно вручную звать деструктор (ну а удалять ничего и не нужно)
Если деструкторы по задаче классу не нужны, лучше не вызывать и не заводить даже пустые.

Ну так пока всё.


--------------------
Ну, я пошёл… Если что – звоните…
Go to the top of the page
 
+Quote Post
 
Start new topic
Ответов
dxp
сообщение Nov 7 2009, 13:51
Сообщение #2


Adept
******

Группа: Свой
Сообщений: 3 469
Регистрация: 6-12-04
Из: Novosibirsk
Пользователь №: 1 343



Цитата(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


--------------------
«Отыщи всему начало, и ты многое поймёшь» К. Прутков
Go to the top of the page
 
+Quote Post
AHTOXA
сообщение Nov 7 2009, 14:05
Сообщение #3


фанат дивана
******

Группа: Свой
Сообщений: 3 387
Регистрация: 9-08-07
Из: Уфа
Пользователь №: 29 684



Цитата(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[]. В чём-то это может быть удобнее.


--------------------
Если бы я знал, что такое электричество...
Go to the top of the page
 
+Quote Post



Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0

 


RSS Текстовая версия Сейчас: 24th July 2025 - 03:26
Рейтинг@Mail.ru


Страница сгенерированна за 0.01382 секунд с 7
ELECTRONIX ©2004-2016