[C][Trick] Resource acquisition
От: andrey.desman  
Дата: 06.04.08 15:05
Оценка: 12 (1)
Навеяно этим топиком: Опять goto :)
Автор: ansi
Дата: 14.01.06


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

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

Обернуть в макрос конструкцию с goto стандартными средствами не представляется возможным, так как генерация имен меток для препроцессора задача непосильная. Так что goto все же идет мимо.
Здесь нас спасет простой по своей сути вариант Владека
Автор: Владек
Дата: 14.01.06
, за что ему отдельное спасибо

В итоге имеем такой костыль:
#define USING(alloc, free, action) \
    if (alloc)                     \
    {                              \
        action;                    \
        free;                      \
    }


Таким образом, пример из оригинального топика примет вид:
typedef struct
{
    int dummy;
    // etc.
} base_rec_t;

typedef struct
{
    base_rec_t base;

    char    *url;
    char    *tag;
    char    *last_mod;
    wchar_t *tmp_name;
    
} record_t;

record_t *alloc_record(size_t url_len, size_t tag_len, size_t lm_len, size_t tmp_len)
{
    record_t *r;

    USING(r = malloc(sizeof(*r)), free(r),
    {
        memset(r, 0, sizeof(*r));

        USING(r->url      = malloc(url_len), free(r->url),
        USING(r->tag      = malloc(tag_len), free(r->tag),
        USING(r->last_mod = malloc(lm_len),  free(r->last_mod),
        USING(r->tmp_name = malloc(tmp_len), free(r->tmp_name),
        {
            /* OK */
            return r;
        }
    }
    )))));

    /* OOPS */
    return 0;
}


И до кучи — копирование файла:
int copy_file(char *src_name, char *dst_name)
{
    FILE *src;
    FILE *dst;
    void *buf;
    size_t sz;
    int result = 0;

    const int B_SIZE = 4096;

    USING(src = fopen(src_name, "rb"), fclose(src),
    USING(dst = fopen(dst_name, "wb"), fclose(dst),
    USING(buf = malloc(B_SIZE), free(buf),
    {
        result = 1;

        while (sz = fread(buf, 1, B_SIZE, src))
        {
            if (fwrite(buf, 1, sz, dst) != sz)
            {
                result = 0;
                break;
            }
        }
    }
    )));

    return result;
}


Недостатки:
Помимо того, что эта штука никоим образом не сандартна, так еще и внутри кода (т.е. action) нельзя использовать запятые вне круглых скобок. Нельзя объявить внутри блока несколько переменных сразу, нельзя проинициализировать массив, а оператор ',' придется заключать в скобки. А если попробуете, то компилятор даст по рукам варнингом неверном количестве аргументов макросу и ошибкой о недостающей '}':
d:\projects\dummy\csource.c(75) : warning C4002: too many actual parameters for macro 'USING'
d:\projects\dummy\csource.c(79) : fatal error C1075: end of file found before the left brace '{' at 'd:\projects\dummy\csource.c(52)' was matched


Но может кому-то пригодится
Re: [C][Trick] Resource acquisition
От: Erop Россия  
Дата: 06.04.08 15:58
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Навеяно этим топиком: Опять goto :)
Автор: ansi
Дата: 14.01.06


AD>В итоге имеем такой костыль:

AD>
AD>#define USING(alloc, free, action) \
AD>    if (alloc)                     \
AD>    {                              \
AD>        action;                    \
AD>        free;                      \
AD>    }
AD>


а чем это лучше вложенного if?
Вроде бы if так же сложен, зато стандартный и запятые можно использовать и накосячить труднее...
Тем более, что alloc часто может не укладываться удобным образом в одно выражение...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: [C][Trick] Resource acquisition
От: remark Россия http://www.1024cores.net/
Дата: 06.04.08 16:32
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>В итоге имеем такой костыль:

AD>
AD>#define USING(alloc, free, action) \
AD>    if (alloc)                     \
AD>    {                              \
AD>        action;                    \
AD>        free;                      \
AD>    }
AD>



А есть опыт применения этой конструкции в реальных проектах?
Посколько такой метод не подразумевает автоматического распространения ошибок вверх по стеку, конструкцией USING придётся изнизать много код. И тут особенно остро встают вопросы несклонности к ошибкам, удобства использования (запятые внутри) и т.д. А без реального опыта сложно отличить надуманные проблемы от серьёзных, и увидеть скрытые проблемы.

Вот ещё можешь посмотреть по теме — я решил запостить в отдельную тему:
http://gzip.rsdn.ru/forum/message/2904912.1.aspx
Автор: remark
Дата: 06.04.08




1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: [C][Trick] Resource acquisition
От: andrey.desman  
Дата: 06.04.08 18:01
Оценка:
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, andrey.desman, Вы писали:


AD>>Навеяно этим топиком: Опять goto :)
Автор: ansi
Дата: 14.01.06


AD>>В итоге имеем такой костыль:

AD>>
AD>>#define USING(alloc, free, action) \
AD>>    if (alloc)                     \
AD>>    {                              \
AD>>        action;                    \
AD>>        free;                      \
AD>>    }
AD>>


E>а чем это лучше вложенного if?

Да по сути это и есть вложенный if. А достоинства я уже описывал (сравни пример Владек'а, и на первый пример в топике):
1. Отдельные уровни вложенности как правило не требуются. Если в if не делать отступы, то читать такой код будет весьма напряжно.
2. Захват ресурса располагается на одной строке с освобождением. Т.е. фактически управление пачкой ресурсов ложится на несколько строк (несколько последовательных USING), а не размазано по функции. Освобождение ресурса не уплывает вниз при добавлении кода, а значит не потеряется из виду.
3. Отсутствие посторонних лексем (шума). В принципе у каждого ресурса две операции — захватить и освободить, на которых хорошо бы иметь визуальный акцент. С этим костылем кроме этих двух операций больше ничего толком и нет. От вложенного if же рябит в глазах.

E>Вроде бы if так же сложен, зато стандартный и запятые можно использовать и накосячить труднее...

E>Тем более, что alloc часто может не укладываться удобным образом в одно выражение...
По мне, так несколько вложенных if на порядок сложнее для восприятия по причинам, которые я указал выше. Где накосячить труднее это еще вопрос, но писать вложенные if сложнее.
Если alloc не укладывается в простое выражение, то возможно стоит это укладывание выделить в отдельную сущность, хотя это не всегда имеет смысл. Я ж не говорю, что есть серебряная пуля
Re[2]: [C][Trick] Resource acquisition
От: andrey.desman  
Дата: 06.04.08 18:27
Оценка:
Здравствуйте, remark, Вы писали:

R>А есть опыт применения этой конструкции в реальных проектах?


Пока все применение сводится к одной функции (просто моя поделка), правда там эта штука модифицирована, чтобы сохранять errno:
int
hexfs_open(const char            *path,
           struct fuse_file_info *fi)
{
    HFS_FILE_DATA_T *fd = NULL;
    FILE            *f = NULL;
    int             e = 0;

    HFS_LOG1("hexfs_open: Enter. Open %s.", path);

    RA(        f = fopen(path, "rb"),      NULL, e, fclose(f),
    RA(       fd = malloc(sizeof(*fd)),    NULL, e, free(fd),
    RA( fd->line = malloc(LINE_WIDTH),     NULL, e, free(fd->line),
    RA(fd->bytes = malloc(BYTES_PER_LINE), NULL, e, free(fd->bytes),
    {
        fd->n_line = -1;
        fd->f = f;
        fd->err_no = 0;

        fi->fh = (intptr_t)fd;

        HFS_LOG("hexfs_open: Exit.");
        return 0;
    }
    ))));

    HFS_LOG("hexfs_open: Exit with fail.");
    return -e;
}


R>Посколько такой метод не подразумевает автоматического распространения ошибок вверх по стеку, конструкцией USING придётся изнизать много код. И тут особенно остро встают вопросы несклонности к ошибкам, удобства использования (запятые внутри) и т.д. А без реального опыта сложно отличить надуманные проблемы от серьёзных, и увидеть скрытые проблемы.


Применимость этого макроса исключительно локальна и на дизайн системы никак не влияет, поэтому об опыте в реальных проектах говорить смысла особого нет. Другой вопрос — это нетривиальные/большие функции. Там да, возможны проблемы. Хотя проблемы там возможны и без использования этого макроса , поэтому у меня таких функций просто нет.
Конструкция проста как бульбулятор и каких-то паталогий кроме возможных проблем с пониманием и проблем с запятыми я бы искать не стал. А вот о них конечно было бы интересно узнать.

R>Вот ещё можешь посмотреть по теме — я решил запостить в отдельную тему:

R>http://gzip.rsdn.ru/forum/message/2904912.1.aspx
Автор: remark
Дата: 06.04.08


R>

Пасиб за интересный линк. Это корабль уже совсем другого плавания со своими достоинствами и заморочками
Re[3]: [C][Trick] Resource acquisition
От: remark Россия http://www.1024cores.net/
Дата: 06.04.08 18:49
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Применимость этого макроса исключительно локальна и на дизайн системы никак не влияет, поэтому об опыте в реальных проектах говорить смысла особого нет. Другой вопрос — это нетривиальные/большие функции. Там да, возможны проблемы. Хотя проблемы там возможны и без использования этого макроса , поэтому у меня таких функций просто нет.

AD>Конструкция проста как бульбулятор и каких-то паталогий кроме возможных проблем с пониманием и проблем с запятыми я бы искать не стал. А вот о них конечно было бы интересно узнать.


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

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


R>>Вот ещё можешь посмотреть по теме — я решил запостить в отдельную тему:

R>>http://gzip.rsdn.ru/forum/message/2904912.1.aspx
Автор: remark
Дата: 06.04.08


R>>

AD>Пасиб за интересный линк. Это корабль уже совсем другого плавания со своими достоинствами и заморочками

Бесспорно.


1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: [C][Trick] Resource acquisition
От: rg45 СССР  
Дата: 06.04.08 19:23
Оценка: :))) :)
Здравствуйте, andrey.desman, Вы писали:

AD>Навеяно этим топиком: Опять goto :)
Автор: ansi
Дата: 14.01.06


AD>С точки зрения семантики данной проблемы goto конечно рулит. Но во-первых, уж слишком много шума он создает вокруг ключевых операций. А во-вторых, требуется вручную следить за порядком захвата/освобождения, а эти операции могут находиться друг от друга на большом расстоянии, что не дает охватить картину в целом. Недостатки очевидны.


AD>Что требуется, так это такая конструкция, в которой:

AD>- сохраняется семантика метода с goto;
AD>- минимум лишнего (т.е. отсутствие шума);
AD>- метод захвата и освобождения находятся рядом (т.е. фактически указываем пару операций одновременно);

AD>Обернуть в макрос конструкцию с goto стандартными средствами не представляется возможным, так как генерация имен меток для препроцессора задача непосильная. Так что goto все же идет мимо.

AD>Здесь нас спасет простой по своей сути вариант Владека
Автор: Владек
Дата: 14.01.06
, за что ему отдельное спасибо


AD>В итоге имеем такой костыль:

AD>
AD>#define USING(alloc, free, action) \
AD>    if (alloc)                     \
AD>    {                              \
AD>        action;                    \
AD>        free;                      \
AD>    }
AD>


Само название топика наводит на мысль о Resource Acquisition Is Initialization (RAII), основная идея которого — получение ресурса в конструкторе, а освобождениие — в деструкторе. Только систематическое и повсеместное применение дает RAII гарантию, что в программе не будет утечки ресурсов. То, что предлагаете Вы — это анти-RAII. Погоня за сомнительным "синтаксическим сахаром" аля-C# создает проблемы на ровном месте:
1. Утечка ресурсов. Если в Вашем примере при вызове fread или fwrite, например, возникнет исключение, то процедуры free и fclose не будут вызваны;
2. Представляю, сколько "теплых" слов произнесет тот, кому прийдется отлаживать код внутри этого самого USING;
3. Макросы — это зло.

Ну зачем изобретать велосипед, если все уже придумано? Например, для выделения/освобождения ресурсов замечательно подходит boost::shared_ptr. Вот как можно реализовать copy_file из твоего примера (забудем на время о цивилизованных способах копирования файлов ради иллюстрации RAII):
#include <cstdio>
#include <stdexcept>
#include <boost\shared_ptr.hpp>

void copy_file(char *src_name, char *dst_name)
{
    const int buf_size = 4096;

    typedef boost::shared_ptr<void> Memory;
    typedef boost::shared_ptr<FILE> File;

    File src(fopen(src_name, "rb"), fclose);
    File dst(fopen(dst_name, "wb"), fclose);
    Memory buf(malloc(buf_size), free);

    while (size_t sz = fread(buf.get(), 1, buf_size, src.get()))
        if (fwrite(buf.get(), 1, sz, dst.get()) != sz)
            throw std::runtime_error("File write error");                
}
... << RSDN@Home 1.2.0 alpha rev. 787>>
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[2]: [C][Trick] Resource acquisition
От: Erop Россия  
Дата: 06.04.08 19:26
Оценка:
Здравствуйте, rg45, Вы писали:

R> Само название топика наводит на мысль о Resource Acquisition Is Initialization (RAII), основная идея которого — получение ресурса в конструкторе, а освобождениие — в деструкторе. Только систематическое и повсеместное применение дает RAII гарантию, что в программе не будет утечки ресурсов. То, что предлагаете Вы — это анти-RAII.

R>Ну зачем изобретать велосипед, если все уже придумано? Например, для выделения/освобождения ресурсов замечательно подходит boost::shared_ptr.

Мне этот "костыль" не нравится, но речь, тем не менее, кажись, идёт о языке С...
Сомневающиеся, например, могут почитать тему сообщения...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: [C][Trick] Resource acquisition
От: remark Россия http://www.1024cores.net/
Дата: 06.04.08 19:33
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Пока все применение сводится к одной функции (просто моя поделка), правда там эта штука модифицирована, чтобы сохранять errno:



Сразу напрашивается сделать спец. макросы под типичные типы ресурсов, что бы можно было писать не так:
RA( fd->line = malloc(LINE_WIDTH), NULL, free(fd->line),
RA( f = fopen(path, "rb"), NULL, fclose(f),


а так:
RA_MALLOC( fd->line, LINE_WIDTH,
RA_FOPEN( f, path, "rb",


Такие макросы будут сразу фиксировать: функцию аллокации, функцию деаллокации и значение, означающее ошибку. Т.к. эти 3 вещи обычно связаны, то смысла их постоянно копи-пастить нет. Эти макросы тем ценнее, чем более широко они будут применяться в проекте.
В таком минималистичном виде их даже, возможно, будут писать по месту, а не копи-пастить



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: [C][Trick] Resource acquisition
От: rg45 СССР  
Дата: 06.04.08 19:36
Оценка: +1 :)
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, rg45, Вы писали:


R>> Само название топика наводит на мысль о Resource Acquisition Is Initialization (RAII), основная идея которого — получение ресурса в конструкторе, а освобождениие — в деструкторе. Только систематическое и повсеместное применение дает RAII гарантию, что в программе не будет утечки ресурсов. То, что предлагаете Вы — это анти-RAII.

R>>Ну зачем изобретать велосипед, если все уже придумано? Например, для выделения/освобождения ресурсов замечательно подходит boost::shared_ptr.

E>Мне этот "костыль" не нравится, но речь, тем не менее, кажись, идёт о языке С...

E>Сомневающиеся, например, могут почитать тему сообщения...

Мда, что называется, слона-то я и не заметил, сорри
... << RSDN@Home 1.2.0 alpha rev. 787>>
--
Не можешь достичь желаемого — пожелай достигнутого.
Re: [C][Trick] Resource acquisition
От: remark Россия http://www.1024cores.net/
Дата: 06.04.08 19:37
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>В итоге имеем такой костыль:

AD>
AD>#define USING(alloc, free, action) \
AD>    if (alloc)                     \
AD>    {                              \
AD>        action;                    \
AD>        free;                      \
AD>    }
AD>



Можно попробовать побороться с запятыми. Навскидку получилось так:
#define EAT(x)
#define EXEC(x) x
#define GET1(x) EXEC(x) EAT
#define GET22(x) EXEC(x)
#define GET2(x) GET22
#define USING(alloc_free, ...)     \
    if (EXEC(GET1 alloc_free))     \
    {                              \
        __VA_ARGS__;               \
        EXEC(GET2 alloc_free);     \
    }


Использовать теперь так:
    USING((p = malloc(1))(free(p)), printf("%p\n", p));


Теперь вроде запятые можно везде ставить.



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.