4

Inspired from the other topic, I wrote this code which simulates a finally block:

#include <cassert>
#include <iostream>

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

class A{
    int a;
public:
    void start(){
        int a=1;        
        lambda finally = [&]{a=2; std::cout<<"finally executed";}; 
        try{
            assert(a==1);
            //do stuff
        }
        catch(int){
            //do stuff
        }
    }
};
int main() {
    A a;
    a.start();
}

Output (ideone):

finally executed

@Johannes seems to think that its not entirely correct, and commented that:

It can crash if the compiler doesn't elide the temporary in the copy initialization, because then it deletes twice with the same pointer value

I would like to know how exactly. Help me understanding the problem :-)


EDIT:

Problem fixed as:

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

    lambda(const lambda&)= delete;            //disable copy ctor
    lambda& operator=(const lambda&)= delete; //disable copy assignment
};

And then use it as:

//direct initialization, no copy-initialization
lambda finally([&]{a=2;  std::cout << "finally executed" << std::endl; }); 

Complete code : http://www.ideone.com/hsX0X

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 1
    What a waste of heap allocation. – Puppy May 29 '11 at 11:40
  • @DeadMG: Actually I was trying to do that with reference, but it didn't work, and I didn't try it again. :| – Nawaz May 29 '11 at 11:43
  • 1
    @Nawaz: Why not just store it as the object it is? `const auto&` will do it just fine. – Puppy May 29 '11 at 12:05
  • @DeadMG: I tried that. But the lamdba is executed! – Nawaz May 29 '11 at 12:07
  • Beware that this doesn't really act like `finally`, if the user puts code between the try block and the function exit, then the lambda executes after that other code. Also, if ScopeGuard (http://drdobbs.com/cpp/184403758) doesn't work with a lambda, then it should be prodded until it does :-) – Steve Jessop May 29 '11 at 13:04
  • @Steve: Yeah. Its a different kind of finally which will be executed no matter what, even if there is no `try-catch`. Its suitable for cases when some invariant checking is done before exiting the function, more like Aspect Oriented Programmming, if I'm not wrong. – Nawaz May 29 '11 at 13:22
  • shameless plug: a C++0xified version of Marginean's scope guard can be seen here: http://pizer.wordpress.com/2008/11/22/scope-guards-revisited-c0x-style/ – sellibitze May 30 '11 at 11:28

2 Answers2

8

In this initialization:

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

The implicitly defined copy constructor for lambda may be used. This will just copy the raw pointer pbase which will then be deleted more than once.

E.g.

$ g++ -std=c++0x -Wall -Wextra -pedantic -fno-elide-constructors lambdafun.cc 
$ ./a.out 
a.out: lambdafun.cc:29: void A::start(): Assertion `a==1' failed.
finally executedAborted (core dumped)

Actually, your assert firing masks the double delete problem, but this demonstrates the crash I was highlighting.

$ g++ -std=c++0x -Wall -Wextra -pedantic -fno-elide-constructors -DNDEBUG lambdafun.cc 
$ ./a.out 
Segmentation fault (core dumped)
CB Bailey
  • 755,051
  • 104
  • 632
  • 656
2

Seems way more complicated than necessary. Why not just:

class finally
{
    std::function<void (void)> const action;
    finally(const finally&) = delete;

public:
    finally(std::function<void (void)> a)
        : action(a)
    {}

    ~finally() { action(); }
};

But in general, one should try not to carry bad Java habits over into C++.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • That is for specific lambda of type `void (*)()`? Can it work with lamda or function of signature `void (*)(int,int)`? – Nawaz Jun 02 '11 at 05:18
  • @Nawaz: Where are the arguments going to come from? The number and type of arguments supplied when the destructor calls the lambda determine the required signature. Your code `~exec() { lambda(); }` permits no arguments. If the lambda needs more data, it needs to capture it. – Ben Voigt Jun 02 '11 at 05:23