开发者

Why is the dtor being called (using annoymous/lambda func)

I am trying to mimic a finally like effect. So i thought i should run a quick dirty test.

The idea was to use Most Important const to stop destruction and to put the finally block in a lambda. However apparently i did something wrong and its being called at the end of MyFinally(). How do i solve this problem?

#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();
}

My Solution code (Note: You can not have two finally in the same block. as expect. But still kind of dirty)

#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{
            as开发者_StackOverflow中文版sert(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);
}

Funny enough, if you remove the & in MyFinally in the original code it works -_-.


// 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); }

You can fix it my introducing a move constructor

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();}
};

And then you can rewrite your toy

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

Hope it helps. No "const reference" trick is needed when you work with auto. See here for how to do it in C++03 with const references.


Your code and Sutter's are not equivalent. His function returns a value, yours returns a reference to an object that will be destroyed when the function exits. The const reference in the calling code does not maintain the lifetime of that object.


The problem stems from the use of a function maker, as demonstrated by Johannes.

I would argue that you could avoid the issue by using another C++0x facility, namely 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;
};

Then, you can use it as simply:

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

  try { assert(a == 1); /**/ } catch(...) { /**/ }
}


Well the problem is explained by others, so I will suggest a fix, exactly in the same way Herb Sutter has written his code (your code is not same as his, by the way):

First, don't return by const reference:

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

Then write this at call site:

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

This became exactly like Herb Sutter's code.

Demo : http://www.ideone.com/uSkhP

Now the destructor is called just before exiting the start() function.


A different implementation which doesn't use auto keyword anymore:

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; }
};

And use it as:

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

Looks interesting?

Complete demo : http://www.ideone.com/DYqrh


You could return a shared_ptr:

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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜