Scope Guard средствами C++0x: часть 1

Часть первая: решение в лоб

Scope Guard — одно из средств автоматического освобождения ресурсов при выходе за пределы видимости переменной, с ними связанной. предоставляет базовую гарантию безопасности исключений. Авторами этой идеи (по-видимому) являются Andrei Alexandrescu и Petru Marginean. Если вы с этой статьёй еще не знакомы, то очень рекомендую к прочтению.

Реализация Scope Guard довольно простая, но из-за того, что C++ не поддерживал шаблоны с переменным количеством параметров, приходилось создавать несколько шаблонов — в зависимости от того, сколько аргументов принимает функция, выполняющая освобождение ресурсов.

В добавлена поддержка шаблонов с переменным количеством параметров (variadic templates). Мне было интересно переписать Scope Guard одним шаблоном — хорошее погружение в новые возможности языка.

Получился примерно такой код (он не самый оптимальный в плане количества строк, можно написать проще, но об этом в следующей части):

[-]
View Code C++
#ifndef SJ_XGUARD_H_
#define SJ_XGUARD_H_

#include <tuple>

namespace {
    template<unsigned int N>
    struct Apply_Helper {
        template<typename F, typename... ArgsT, typename... Args>
        static void apply(const F& f, const std::tuple<ArgsT...>&& t, Args&&... args)
        {
            Apply_Helper<N-1>::apply(f, t, std::get<N-1>(t), args...);
        }
    };

    template<>
    struct Apply_Helper<0> {
        template<typename F, typename... ArgsT, typename... Args>
        static void apply(const F& f, const std::tuple<ArgsT...>&&, Args&&... args)
        {
            f(args...);
        }
    };

    template<typename F, typename... ArgsT>
    void apply(const F& f, std::tuple<ArgsT...> const&& t)
    {
        Apply_Helper<sizeof...(ArgsT)>::apply(f, t);
    }
};

template<typename Function, typename... Args>
class XGuard {
public:
    XGuard(const Function& aFunction, std::tuple<Args...>&& aParams)
        : m_fSucceeded(false),
          m_Function(aFunction),
          m_Params(aParams)
    {
    }

    ~XGuard(void) throw()
    {
        if (!this->m_fSucceeded) {
            try {
                apply(this->m_Function, this->m_Params);
            }
            catch(...) {
            }
        }
    }

    XGuard(XGuard&& other)
        : m_fSucceeded(other.m_fSucceeded),
          m_Function(other.m_Function),
          m_Params(other.m_Params)
    {
        other.commit();
    }

    void commit(void) const throw()
    {
        this->m_fSucceeded = true;
    }

    XGuard& operator=(XGuard&& other)
    {
        if (&other != this) {
            this->m_fSucceeded = other.m_fSucceeded;
            this->m_Function   = other.m_Function;
            this->m_Params     = std::move(other.m_Params);

            other.m_Function = NULL;
            other.commit();
        }

        return *this;
    }

    XGuard(const XGuard& other) = delete;
    XGuard& operator=(const XGuard& other) = delete;
private:
    mutable bool m_fSucceeded;
    Function m_Function;
    std::tuple<Args...>&& m_Params;
};

template<typename F, typename... A>
inline XGuard<F, A...> makeXGuard(const F& f, std::tuple<A...>&& p)
{
    return XGuard<F, A...>(f, p);
}

#define MAKEXGUARD(f, ...) \
    makeXGuard((f), std::make_tuple(__VA_ARGS__))


#endif /* SJ_XGUARD_H_ */

Название XGuard чисто историческое, я в университете занимался «велосипедостроительством», поэтому просто переделал код многолетней давности.

Основной трудностью для меня было развернуть std::tuple в список параметров. Отвык программировать на Prolog :-( Развёрткой std::tuple в список параметров занимается Apply_Helper в анонимном пространстве имён. Для программировавших на чем-нибудь типа Prolog принцип очень простой:

[-]
View Code Text
Apply_Helper<N>::apply([tuple_head|tuple_tail], [params]) => Apply_Helper<N-1>::apply([tuple_tail], [tuple_head|params])

Другой подводный камень — макро MAKEXGUARD. Дело в том, что такая реализация:

[-]
View Code C++
template<typename F, typename... A>
inline XGuard<F, A...> makeXGuard(const F& f, const A&& p)
{
    return XGuard<F, A...>(f, std::make_tuple(p));
}

не воспринимает std::ref()/std::cref() в списках параметров — у компилятора не получается определить корректный шаблон на основе вывода параметров.

Как я уже говорил, приведённый код не является оптимальным в плане количества строк (можно сделать проще), но не генерирует лишнего машинного кода. Вся свёртка/развёртка параметров производится на этапе компиляции.

Например, такой код:

[-]
View Code C++
#include <cstdlib>
//#include <iostream>
#include <functional>
#include "xguard.h"

static void my_free(void*& p)
{
    if (p) {
        free(p);
        p = NULL;
    }
}

int main(int, char**)
{
    {
        void* p = (void*)std::malloc(100);
//      std::cout << p << std::endl;
        auto guard = MAKEXGUARD(&my_free, std::ref(p));
//      std::cout << p << std::endl;
    }

    return 0;
}

транслируется в такой:

[-]
View Code ASM
main:
    sub rsp, 24
    mov edi, 100
    call    malloc          ; void* p = (void*)std::malloc(100);
    lea rdi, [rsp+8]
    mov QWORD PTR [rsp+8], rax
    call    _ZL7my_freeRPv      ; my_free(p);
    xor eax, eax
    add rsp, 24
    ret

_ZL7my_freeRPv:
    push    rbx
    mov rbx, rdi
    mov rdi, QWORD PTR [rdi]
    test    rdi, rdi
    je  .L5
    call    free
    mov QWORD PTR [rbx], 0
.L5:
    pop rbx
    ret

Таким образом, вызов функции makeXGuard() и конструктора XGuard::XGuard() вообще не генерирует никакого кода! А при выходе p из области видимости автоматически генерируется вызов my_free(p) (то есть то же самое, что и написал бы программист, не использующий Scope Guard). Иными словами, накладных расходов нет. Красота!

Автор: ; опубликовано в: C/C++; метки: C++0x, C/C++, Scope Guard
23
Апр
2010

RSS Комментарии к статье «Scope Guard средствами C++0x: часть 1» (2)  »

  1. [...] This post was mentioned on Twitter by Интернет заработок. Интернет заработок said: V.Kolesnikov: Scope Guard средствами C++0x [...]

  2. [...] В прошлой части была рассмотрена реализация Scope Guard средствами C++0x. Благодаря шаблонам с переменным количеством параметров (variadic templates), реализация на C++0x получилась несколько проще, чем в оригинале, так как один и тот же шаблонный класс может использоваться для создания Scope Guard с различным количеством параметров. [...]

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

Оставить комментарий к записи «Scope Guard средствами C++0x: часть 1»

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Оставляя комментарий, вы выражаете своё согласие с Правилами комментирования.

Подписаться, не комментируя

गते गते पारगते पारसंगते बोधि स्वाहा