RAII && try/finalize
От: yaser Украина  
Дата: 13.02.16 19:50
Оценка:
Добрый вечер.

Есть некий класс:
class TSomeObject {
private:
  int * data = nullptr;
public:
  .......
  void play(int* external_data) {
     data = external_data;
     ....
     some_action();
     ....
     data = nullptr;
  }
  void some_action() {
     ....
     throw std::exception("very bad code");
  }  
}

Какой-то внешний код вызывает метод play, передавая указатель на внешние данные. чтобы не передавать по всем методам класса эти данные, приняли решение их сохранить на уровне класса.
Хотелось бы чтобы после завершения метода play data был равен nullptr. Понимаю что можно сделать try {... data = nullptr; } catch (...){data = nullptr; throw; }, но мне кажется это не очень правильный патерн в рамках идеологии RAII. Хотелось бы понять какие патерны вы используете в такой ситуации. Заранее ответивщим спасибо.
Отредактировано 14.02.2016 12:13 Кодт . Предыдущая версия .
Re: RAII && try/finalize
От: Patalog Россия  
Дата: 13.02.16 20:12
Оценка: 3 (1)
Здравствуйте, yaser, Вы писали:

Y>Есть некий класс:


Y>class TSomeObject {
Y>private:
Y>  int * data = nullptr;
Y>public:
Y>  .......
Y>  void play(int* external_data) {
Y>     data = external_data;
Y>     ....
Y>     some_action();
Y>     ....
Y>     data = nullptr;
Y>  }
Y>  void some_action() {
Y>     ....
Y>     throw std::exception("very bad code");
Y>  }  
Y>}


Не благодари.
Почетный кавалер ордена Совка.
Re: RAII && try/finalize
От: watchmaker  
Дата: 13.02.16 20:13
Оценка:
Здравствуйте, yaser, Вы писали:


Y>Хотелось бы понять какие патерны вы используете в такой ситуации. Заранее ответивщим спасибо.


Ну а какие тут ещё варианты? либо используешь одну из многочисленных реализаций scope_exit, либо пишешь свою простую adhoc версию:
template <class T>
class NullableGuard 
{
    T*& Holder;
public:    
    NullableGuard(T*& holder, T* init)
        : Holder(holder) 
    {
        holder = init;
    }
    
    ~NullableGuard() 
    {
        Holder = nullptr;
    }
};


...

void TSomeObject::play(int* external_data) {
    NullableGuard<int> dataGuard(data, external_data);
    some_action();
}
Re: RAII && try/finalize
От: Vamp Россия  
Дата: 13.02.16 21:30
Оценка:
Я бы сделал так:

#include <memory>
#include <functional>

void g();

int* ptr = nullptr;

int forty_two = 42;

void foo() {
  ptr = &forty_two;
  auto deleter = [&ptr](int* ) { ptr = nullptr; };
  std::unique_ptr<int, decltype(deleter)> guard(ptr, deleter);  
}

#include <iostream>
int main() {
    std::cout << "P: " << ptr << "\n";
    foo();
    
    std::cout << "P: " << ptr << "\n";
}
Да здравствует мыло душистое и веревка пушистая.
Отредактировано 13.02.2016 21:30 Vamp . Предыдущая версия .
Re: RAII && try/finalize
От: rg45 СССР  
Дата: 14.02.16 03:40
Оценка: 2 (2) +1
Здравствуйте, yaser, Вы писали:

Y>Какой-то внешний код вызывает метод play, передавая указатель на внешние данные. чтобы не передавать по всем методам класса эти данные, приняли решение их сохранить на уровне класса.

Y>Хотелось бы чтобы после завершения метода play data был равен nullptr. Понимаю что можно сделать try {... data = nullptr; } catch (...){data = nullptr; throw; }, но мне кажется это не очень правильный патерн в рамках идеологии RAII. Хотелось бы понять какие патерны вы используете в такой ситуации. Заранее ответивщим спасибо.

BOOST_SCOPE_EXIT

#include <boost/scope_exit.hpp>

class TSomeObject {
private:
  int * data = nullptr;
public:
  void play(int* external_data) {

     BOOST_SCOPE_EXIT(&data)
     {
        data = nullptr;
     }
     BOOST_SCOPE_EXIT_END;

     data = external_data;

     some_action();
  }  
}


Возможно еще такое использование, если членов-данных много

     BOOST_SCOPE_EXIT(this_)
     {
        this_->data = nullptr;
     }
     BOOST_SCOPE_EXIT_END;


А вообще, лично я избегаю делать членами класса данные, которые бесполезны вне вызова какой-то одной функции (play, в нашем случае). Это явное показание к пересмотру дизайна.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 14.02.2016 3:58 rg45 . Предыдущая версия . Еще …
Отредактировано 14.02.2016 3:56 rg45 . Предыдущая версия .
Отредактировано 14.02.2016 3:53 rg45 . Предыдущая версия .
Отредактировано 14.02.2016 3:50 rg45 . Предыдущая версия .
Re[2]: RAII && try/finalize
От: uncommon Ниоткуда  
Дата: 14.02.16 07:52
Оценка: 1 (1)
Здравствуйте, rg45, Вы писали:

R>BOOST_SCOPE_EXIT


А можно вот так: http://rsdn.ru/forum/cpp/5991672.1
Автор: Evgeny.Panasyuk
Дата: 23.03.15
. Не люблю Boost.ScopeExit из-за её монстроидальной реализации.
Re[3]: RAII && try/finalize
От: rg45 СССР  
Дата: 14.02.16 08:38
Оценка:
Здравствуйте, uncommon, Вы писали:

U>Не люблю Boost.ScopeExit из-за её монстроидальной реализации.


Да я согласен. Конечно же, C++0x предоставляет возможности написать все значительно привлекательнее. Но, в то же время, у BOOST_SCOPE_EXIT есть одно важное преимущество — он написан в библиотеке самого общего использования и избавляет от засоренияя кода собстенными Guard-ами.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 14.02.2016 8:45 rg45 . Предыдущая версия .
Re: RAII && try/finalize
От: kr510  
Дата: 14.02.16 09:23
Оценка: -3 :)
Здравствуйте, yaser, Вы писали:

Y>Хотелось бы чтобы после завершения метода play data был равен nullptr. Понимаю что можно сделать try {... data = nullptr; } catch (...){data = nullptr; throw; }, но мне кажется это не очень правильный патерн в рамках идеологии RAII.


try-catch вполне себе работает, читаем и достаточно компактен. Значит паттрерн правильный
Re[2]: RAII && try/finalize
От: rg45 СССР  
Дата: 14.02.16 09:59
Оценка:
Здравствуйте, kr510, Вы писали:

K>try-catch вполне себе работает, читаем и достаточно компактен. Значит паттрерн правильный


Блок catch отработает только при выбрасывании исключения. Автору же топика, если я правильно понимаю, нужна секция кода, которая гарантированно отработает при любом сценарии выхода из скоупа. Т.е. нечто равноценное finally или RAII.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[3]: RAII && try/finalize
От: kr510  
Дата: 14.02.16 10:10
Оценка: :)
Здравствуйте, rg45, Вы писали:

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


K>>try-catch вполне себе работает, читаем и достаточно компактен. Значит паттрерн правильный


R>Блок catch отработает только при выбрасывании исключения. Автору же топика, если я правильно понимаю, нужна секция кода, которая гарантированно отработает при любом сценарии выхода из скоупа. Т.е. нечто равноценное finally или RAII.


Ну напиши что-то вроде

try
{

}
catch(...)
{
   cleanup();
   throw;
}
cleanup();
Re[4]: RAII && try/finalize
От: rg45 СССР  
Дата: 14.02.16 10:15
Оценка: +2
Здравствуйте, kr510, Вы писали:

K>Ну напиши что-то вроде


K>
K>try
K>{

K>}
K>catch(...)
K>{
K>   cleanup();
K>   throw;
K>}
K>cleanup();
K>


Это ты автору топика предложи. Вариант, конечно, очевидный, только я сомневаюсь, что это то, о чем он мечтал. Внутири cleanup нет никакой связи с локальными объектами вызывающей фунции. Плодить для каждого метода свой cleanup — вариант не слишком привлекательный. И то, что вызов cleanup не достаточно сделать один раз, а надо не забыть его вставить в каждой точке возврата — тоже не есть хорошо.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 14.02.2016 10:41 rg45 . Предыдущая версия . Еще …
Отредактировано 14.02.2016 10:40 rg45 . Предыдущая версия .
Отредактировано 14.02.2016 10:35 rg45 . Предыдущая версия .
Отредактировано 14.02.2016 10:18 rg45 . Предыдущая версия .
Re[5]: RAII && try/finalize
От: kr510  
Дата: 14.02.16 11:03
Оценка: -1
Здравствуйте, rg45, Вы писали:

R>Внутири cleanup нет никакой связи с локальными объектами вызывающей фунции.


cleanup может быть либо лямдой, если "void play()" только один, либо методом TSomeObject.

R>И то, что вызов cleanup не достаточно сделать один раз, а надо не забыть его вставить в каждой точке возврата — тоже не есть хорошо.


Точек куда надо вставить только 2: выход по exception и выход по успеху. Это предельно простой и надежный код.
Re[6]: RAII && try/finalize
От: rg45 СССР  
Дата: 14.02.16 11:51
Оценка:
Здравствуйте, kr510, Вы писали:

R>>Внутири cleanup нет никакой связи с локальными объектами вызывающей фунции.

K>cleanup может быть либо лямдой, если "void play()" только один, либо методом TSomeObject.

А можешь привести пример, как ты собираешься вызвать в блоке catch лямбду, определенную в блоке try?

И потом, это уже не просто catch, как ты предлагал изначально, а catch плюс лямбды. Еще один шажок и приходим к тому, что лямбду можно поместить в деструктор какого-нибудь объекта, как предлагали здесь
Автор: uncommon
Дата: 14.02.16
. А там, глядишь, и придем к выводу, что решение с catch не единственное, а, возможно даже, не самое лучшее.

K>Точек куда надо вставить только 2: выход по exception и выход по успеху. Это предельно простой и надежный код.


Ну во-первых две это уже хуже, чем одна. А во-вторых, остается таки открытым вопрос, что делать в случаях, когда точек возврата больше, чем две. Например, захотели мы добавить еще один блок catch для std::exception, как быть? Нормальное же желание, нет ничего предосудительного?
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 14.02.2016 11:56 rg45 . Предыдущая версия .
Re[2]: RAII && try/finalize
От: _hum_ Беларусь  
Дата: 14.02.16 12:05
Оценка:
Здравствуйте, watchmaker, Вы писали:

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



Y>>Хотелось бы понять какие патерны вы используете в такой ситуации. Заранее ответивщим спасибо.


W>Ну а какие тут ещё варианты? либо используешь одну из многочисленных реализаций scope_exit, либо пишешь свою простую adhoc версию:
template <class T>
W>class NullableGuard 
W>{
W>    T*& Holder;
W>public:    
W>    NullableGuard(T*& holder, T* init)
W>        : Holder(holder) 
W>    {
W>        holder = init;
W>    }
    
W>    ~NullableGuard() 
W>    {
W>        Holder = nullptr;
W>    }
W>};


W>...

W>void TSomeObject::play(int* external_data) {
W>    NullableGuard<int> dataGuard(data, external_data);
W>    some_action();
W>}

W>


Вроде ж человек хочет по RAII. А по нему

In RAII, holding a resource is tied to object lifetime: resource allocation (acquisition) is done during object creation (specifically initialization), by the constructor, while resource deallocation (release) is done during object destruction, by the destructor.

В его случае под овладением ресурсом можно понимать овладение указателем на истоник данных. Ну так, чего ж тогда мучаться — нужно просто и создать указатель на этот указатель:
class TSomeObject {
 private:
   std::weak_ptr<int*> m_wppData;
 public:
   //.......
   void play(int* external_data) {
     

      auto sppData = std::make_shared<int*>(external_data);
      m_wppData    = sppData;
      //<....>
      some_action();
      //<....>
   }
   void some_action() {
      //<....>
      throw std::exception("very bad code");
   }  
 }
Отредактировано 14.02.2016 15:05 _hum_ . Предыдущая версия .
Re[7]: RAII && try/finalize
От: kr510  
Дата: 14.02.16 12:36
Оценка:
Здравствуйте, rg45, Вы писали:

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


R>>>Внутири cleanup нет никакой связи с локальными объектами вызывающей фунции.

K>>cleanup может быть либо лямдой, если "void play()" только один, либо методом TSomeObject.

R>А можешь привести пример, как ты собираешься вызвать в блоке catch лямбду, определенную в блоке try?


class TSomeObject {
private:
    int * data ;
public:
    void play(int* external_data) {

        auto init = [&]{ data = external_data; };
        auto cleanup = [&]{ data = nullptr; };

        init();

        try{
            some_action();
        }
        catch (...){
            cleanup();
            throw;
        }
        cleanup();
    }
    void some_action() {
            throw std::exception("very bad code");
    }
};
Re[8]: RAII && try/finalize
От: rg45 СССР  
Дата: 14.02.16 13:01
Оценка: +1
Здравствуйте, kr510, Вы писали:

R>>А можешь привести пример, как ты собираешься вызвать в блоке catch лямбду, определенную в блоке try?


K>...

K>class TSomeObject {
K>private:
K> int * data ;
K>public:
K> void play(int* external_data) {

K> auto init = [&]{ data = external_data; };

K> auto cleanup = [&]{ data = nullptr; };

K> init();


K> try{

K> some_action();
K> }
K> catch (...){
K> cleanup();
K> throw;
K> }
K> cleanup();
K> }
K> void some_action() {
K> throw std::exception("very bad code");
K> }
K>};
K>[/ccode]

И где здесь лямбда, определенная в блоке try? Я, вообще полагал, что мы обсуждаем более общий случаю, чем приведен в стартовом сообщении. А в более общем случае лямбдам бывает нужно захватывать объекты из локального скоупа. А расширение скоупа объектов, вообще, не есть хорошо, а в некоторых случаях просто не приемлемо.

А главное, не понятна твоя настойчивость в отстаивании варианта с catch. Ну недостатков же целый ряд — как уже упомянутых, так и неупомянутых. А преимущества? Какие преимущества у варианта с catch перед вариантами с RAII?
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 14.02.2016 13:04 rg45 . Предыдущая версия .
Re[2]: RAII && try/finalize
От: Vamp Россия  
Дата: 14.02.16 13:11
Оценка:
R>BOOST_SCOPE_EXIT
Ну какой еще scope_exit в 2016 году???
Да здравствует мыло душистое и веревка пушистая.
Re[3]: RAII && try/finalize
От: rg45 СССР  
Дата: 14.02.16 13:49
Оценка:
Здравствуйте, Vamp, Вы писали:

V>Ну какой еще scope_exit в 2016 году???


Я, конечно же, в курсе, что лямбду можно запихнуть в умный указатель. Только для регулярного использования я бы предпочел что-нибудь более заточенное для этих целей. Реализуется и просто и юзабельно, странно, что в стандарной библиотеке нет ничего подобного. Или я чего-то не знаю?
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[4]: RAII && try/finalize
От: Vamp Россия  
Дата: 14.02.16 14:06
Оценка:
Ну вот умный указатель и есть в библиотеке. А что еще надо?
Да здравствует мыло душистое и веревка пушистая.
Re[5]: RAII && try/finalize
От: rg45 СССР  
Дата: 14.02.16 14:56
Оценка: +2
Здравствуйте, Vamp, Вы писали:

V>Ну вот умный указатель и есть в библиотеке. А что еще надо?


По конечному результату именно оно и надо. Но по форме хотелось бы, чтобы выглядело как-то так: http://rsdn.ru/forum/cpp/5991672.1
Автор: Evgeny.Panasyuk
Дата: 23.03.15
--
Не можешь достичь желаемого — пожелай достигнутого.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.