Почему вызывается dtor (с использованием функции annoymous / lambda) - PullRequest
5 голосов
/ 29 мая 2011

Я пытаюсь имитировать наконец-то похожий эффект.Поэтому я подумал, что мне следует провести быстрый грязный тест.

Идея заключалась в том, чтобы использовать Наиболее важный констант , чтобы остановить разрушение и поместить блок finally в лямбду.Однако, по-видимому, я сделал что-то не так, и его вызывали в конце MyFinally ().Как мне решить эту проблему?

#include <cassert>
template<typename T>
class D{
    T fn;
public:
    D(T v):fn(v){}
    ~D(){fn();}
};

template<typename T>
const D<T>& MyFinally(T t) { return D<T>(t); }

int d;
class A{
    int a;
public:
    void start(){
        int a=1;
        auto v = MyFinally([&]{a=2;});
        try{
            assert(a==1);
            //do stuff
        }
        catch(int){
            //do stuff
        }
    }
};
int main() {
    A a;
    a.start();
}

Мой код решения (Примечание: вы не можете иметь два, наконец, в одном и том же блоке. Как и ожидалось. Но все еще немного грязно)

#include <cassert>
template<typename T>
class D{
    T fn; bool exec;
public:
    D(T v):fn(v),exec(true){}
    //D(D const&)=delete //VS doesnt support this yet and i didnt feel like writing virtual=0
    D(D &&d):fn(move(d.fn)), exec(d.exec) {
      d.exec = false;
    }

    ~D(){if(exec) fn();}
};
template<typename T>
D<T> MyFinally(T t) { return D<T>(t); }


#define FINALLY(v) auto OnlyOneFinallyPlz = MyFinally(v)

int d;
class A{
public:
    int a;
    void start(){
        a=1;
        //auto v = MyFinally([&]{a=2;});
        FINALLY([&]{a=2;});
        try{
            assert(a==1);
            //do stuff
        }
        catch(int){
            FINALLY([&]{a=3;}); //ok, inside another scope
            try{
                assert(a==1);
                //do other stuff
            }
            catch(int){
                //do other stuff
            }
        }
    }
};
void main() {
    A a;
    a.start();
    assert(a.a==2);
}

Достаточно забавно, если вы удалите & в MyFinally в исходном коде, он работает -_-.

Ответы [ 5 ]

5 голосов
/ 29 мая 2011
// WRONG! returning a reference to a temporary that will be
// destroyed at the end of the function!
template<typename T>
const D<T>& MyFinally(T t) { return D<T>(t); }

Вы можете это исправить, введя конструктор перемещения

template<typename T>
class D{
    T fn;
    bool exec;

public:
    D(T v):fn(move(v)),exec(true){}

    D(D &&d):fn(move(d.fn)), exec(d.exec) {
      d.exec = false;
    }

    ~D(){if(exec) fn();}
};

А потом вы можете переписать свою игрушку

template<typename T>
D<T> MyFinally(T t) { return D<T>(move(t)); }

Надеюсь, это поможет. При работе с auto трюк с "const reference" не требуется. См. здесь , чтобы узнать, как это сделать в C ++ 03 с константными ссылками.

2 голосов
/ 29 мая 2011

Проблема связана с использованием создателя функции, как продемонстрировал Йоханнес.

Я бы сказал, что вы можете избежать этой проблемы, используя другое средство C ++ 0x, а именно std::function.

class Defer
{
public:
  typedef std::function<void()> Executor;

  Defer(): _executor(DoNothing) {}

  Defer(Executor e): _executor(e) {}
  ~Defer() { _executor(); }

  Defer(Defer&& rhs): _executor(rhs._executor) {
    rhs._executor = DoNothing;
  }

  Defer& operator=(Defer rhs) {
    std::swap(_executor, rhs._executor);
    return *this;
  }

  Defer(Defer const&) = delete;

private:
  static void DoNothing() {}
  Executor _executor;
};

Затем вы можете использовать его просто:

void A::start() {
  a = 1;
  Defer const defer([&]() { a = 2; });

  try { assert(a == 1); /**/ } catch(...) { /**/ }
}
2 голосов
/ 29 мая 2011

Ваш код и код Саттера не эквивалентны.Его функция возвращает значение, а ваша возвращает ссылку на объект, который будет уничтожен при выходе из функции.Ссылка const в вызывающем коде не поддерживает время жизни этого объекта.

1 голос
/ 29 мая 2011

Что ж, проблема объясняется другими, поэтому я предложу исправить, точно так же, как Херб Саттер написал свой код (кстати, ваш код не такой, как у него):

Первый, не возвращайтесь по постоянной ссылке:

template<typename T>
D<T> MyFinally(T t) 
{
   D<T> local(t); //create a local variable
   return local; 
}

Затем напишите это на сайте вызова:

const auto & v = MyFinally([&]{a=2;}); //store by const reference

Это стало в точности как код Херба Саттера .

Демо: http://www.ideone.com/uSkhP

Теперь деструктор вызывается непосредственно перед выходом из функции start().


Другая реализация, которая не использует autoКлючевое слово больше:

struct base { virtual ~base(){} };

template<typename TLambda>
struct exec : base 
{
   TLambda lambda;
   exec(TLambda l) : lambda(l){}
   ~exec() { lambda(); }
};

class lambda{
    base *pbase;
public:
    template<typename TLambda>
    lambda(TLambda l): pbase(new exec<TLambda>(l)){}
    ~lambda() { delete pbase; }
};

И использовать его как:

lambda finally = [&]{a=2;  std::cout << "finally executed" << std::endl; }; 

Выглядит интересно?

Полная демонстрация: http://www.ideone.com/DYqrh

0 голосов
/ 29 мая 2011

Вы можете вернуть shared_ptr:

template<typename T>
std::shared_ptr<D<T>> MyFinally(T t) {
    return std::shared_ptr<D<T>>(new D<T>(t));
}
...