Tuesday, October 19, 2010

Об исключениях и С++

    В С++ есть исключения и есть RAII техники. Теперь абстрактная задача:
Есть некие внешние функции Start, Stop и Task. Мы с ними работаем в таком порядке: Start вызывать нужно до вызова Task а Stop - после(причем Stop вызывать нужно обязательно, но только, если был вызван Start - строгое условие). Клиентский код мы изменить не можем.

    Теперь более подробнее - некий псевдо-код задачи:


try
{
    if (NULL != p)
    {
        p->Start(); // может бросить исключение

        // Некий опасный код бросающий исключения...

        DangerTask(); // может бросить исключение

        // Некий опасный код бросающий исключения...

        p->Stop(); // может бросить исключение
    }
    else
    {
        // Некий опасный код бросающий исключения...

        DangerTask(); // может бросить исключение, а также - повторение кода

        // Некий опасный код бросающий исключения...
    }
}
catch(const std::exception&)
{
    // И что делать? Мы должны вызывать p->Stop() или нет?
}
catch(...)
{
    // И что делать? Мы должны вызывать p->Stop() или нет?
}

    Этот псевдокод плох тем, что имеется повторение кода, неоднозначность в обработке ошибок, небезопасный код. В большинстве случаев такое прокатывает - т.к. важно чтоб заработало все побыстрее, и был низкий уровень входимости/опыта для суппорта, и да - уровень входимости ниже, но если код растет линейно - то время на его добавление экспонециально растет. Но сейчас не об этом, нужно просто написать правильный транзакционный код, и без повторений кода (см. выше - DangerTask в коде дважды вызывается).
    Это довольно типичная задача. В большинстве случаев люди на это(возможные исключения и повторения кода) не обращают внимание(т.к. они в своем коде не бросают исключения - а думают что если оно произошло - то это мол редкость и вообще уже мало что можно сделать в этом случае). Не буду говорить о вероятности и т.п. Суть в том - что нам нужно обеспечить строгую гарантию безсбойного транзакционного режима Start/Stop, и мы допускаем что чужой код и наш код весьма бросает исключения.
    Задача на самом деле не сложная. Апологеты ООП сразу вспомнят про RAII - и наплодят пару-тройку классов которые собой обернут эту семантику. В принципе - это всегда хорошее решение. Но вот мне не хочется писать каждый раз классы-обертки. Также не хочу использовать сторонние библиотеки. Для этой цели(чтоб не плодить новое) можно использовать дополнения к STL - std::tr1::shared_ptr и std::tr1::bind вот так:

try
{
    // Некий опасный код бросающий исключения...

    // Это сложная атомарная операция, которую нельзя испортить
       своим новым небезопасным кодом

    p && std::tr1::shared_ptr < void >
        ((p->Start(), LPVOID(NULL)), std::tr1::bind(&IPolicy::Stop, p)),
            DangerTask();
// может бросить исключение

    // Некий опасный код бросающий исключения...
}
catch(const std::exception&)
{
    // Делаем что что-либо, но мы уже не думаем про p->Stop()
}
catch(...)
{
    // Делаем что что-либо, но мы уже не думаем про p->Stop()
}

Также можно заюзать ninja_ptr с предыдущего поста:

try
{
    // Некий опасный код бросающий исключения...

    p && ninja_ptr < IPolicy > ((p->Start(), p), &IPolicy::Stop),
        DangerTask(); // может бросить исключение

    // Некий опасный код бросающий исключения...
}
catch(const std::exception&)
{
    // Делаем что что-либо, но мы уже не думаем про p->Stop()
}
catch(...)
{
    // Делаем что что-либо, но мы уже не думаем про p->Stop()
}

Вот такие приколы. Код я переделал с реального, но не скомпилировал, надеюсь "скобочки" все на месте.

Monday, October 18, 2010

scoped_ptr

Недавно начал читать Александреску. Вот в процессе первых двух глав навалял свой scoped_ptr с custom deleter:


#ifndef __CMN_SCOPED_PTR_H__
#define __CMN_SCOPED_PTR_H__

#include "cmnNonCopyable.h"

namespace utils
{
    template < typename T, class Copyable = utils::noncopyable >
    class scoped_ptr : Copyable
    {
        struct deleter_base
        {
            virtual void release() = 0;
        } * Dt_;

        template < typename _Dx >
        class deleter
            : public deleter_base
            , Copyable
        {
            T*  p_;
            _Dx Dt_;

            template < int v > struct int2type { enum { value = v }; };

            template < typename _Type >
            class is_mem_func_ptr
            {
                template < typename T0 >
                struct is_mem_func_ptr_ { enum { res = 0 }; };

                template < typename R, typename S >
                struct is_mem_func_ptr_ < R (S::*)( ) > { enum { res = 1 }; };
            public:
                enum { res = is_mem_func_ptr_ < _Type >::res };
            };

            template < typename _DxiEx >
            void deleter_internal_ex(_DxiEx Dt, int2type < 1 >)
            {
                (*p_.*Dt)();
            }

            template < typename _DxiEx >
            void deleter_internal_ex(_DxiEx Dt, int2type < 0 >)
            {
                Dt(p_);
            }

            template < typename _Dxi >
            void deleter_internal(_Dxi Dt)
            {
                deleter_internal_ex(Dt, int2type < is_mem_func_ptr < _Dxi >::res >( ));
            }

            template < typename _Ret >
            void deleter_internal(_Ret (*Dt) ( ))
            {
                Dt();
            }

        public:
            deleter(_Dx Dt, T* p)
                : p_(p)
                , Dt_(Dt)
            { }

            virtual void release()
            {
                deleter_internal(Dt_);
            }
        };

    public:
        scoped_ptr()
            : Dt_(NULL)
            , array_(false)
            , p_(NULL)
        { }

        explicit scoped_ptr(T* p, bool array = false)
            : Dt_(NULL)
            , array_(array)
            , p_(p)
        { }

        template < typename _Dx >
        scoped_ptr(T* p, _Dx Dt)
            : Dt_(new deleter < _Dx > (Dt, p))
            , array_(false)
            , p_(NULL)
        { }

        ~scoped_ptr() { free(); }
        operator T* () { return p_; }
        T* operator->() { return p_; }

        T* release()
        {
            T* p = p_;
            p_ = NULL;

            return p;
        }

        void reset(T* p = NULL)
        {
            if (p != p_)
            {
                free();
            }

            p_ = p;
        }

        T* get() { return p_; }

    private:
        void free()
        {
            if (NULL == Dt_)
            {
                if (array_)
                {
                    delete [] p_;
                }
                else
                {
                    delete p_;
                }
            }
            else
            {
                Dt_->release();
            }
        }

    private:
        bool array_;
        T* p_;
    };
}

#ifdef SMALTI_NINJA
    #define ninja_ptr utils::scoped_ptr
#endif

#endif

//-----------------------------------------------------------------------------

Тест:

 
#include "stdafx.h"
#include

#define SMALTI_NINJA
#include "cmnScopedPtr.h"

struct ITest
{
    virtual void Release() volatile = 0;
};

class CTest : public ITest
{
    virtual void Release() volatile { delete this; }

public:
    static ITest* Create() { return new CTest; }
};

void Save(ITest* p) { }

class CSimple
{ };

int _tmain(int, _TCHAR* [])
{
    ninja_ptr < int > pInt(new int);
    ninja_ptr < CSimple > pSimple(new CSimple);
    ninja_ptr < CSimple > pSimpleArray(new CSimple[100], true);
    ninja_ptr < void >  pMalloc(::malloc(100), &::free);
    ninja_ptr < void >  pAbort((void*)0, &::abort);
    ninja_ptr < ITest > pRelease(CTest::Create(), &ITest::Release);
    ninja_ptr < ITest > pAutoSave(CTest::Create(), Save);

    return 0;
}