5

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

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

Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • nice thought! You could also call this `defer` (like in Go), although it's never clear until when it is deferred... – Matthieu M. May 29 '11 at 10:15
  • Join this topic : [Simulating finally block in C++0x](http://stackoverflow.com/questions/6167515/simulating-finally-block-in-c0xx) – Nawaz May 29 '11 at 11:39

5 Answers5

5
// 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.

Community
  • 1
  • 1
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • I've one doubt : `move` in `return D(move(t));` is necessary? Why exactly?.... and in *D(T v): **fn(move(v))** ,exec(true){}*? – Nawaz May 29 '11 at 13:28
  • @Nawaz because those are not among the cases where a move is done automatically. – Johannes Schaub - litb May 29 '11 at 13:33
  • In my solution, I'm using `pointer-to-base`; I just don't like it; I want to use reference instead. So can I use `move` to accomplish that? I'm trying, but not able to do that; never worked with `std::move` before : http://stackoverflow.com/questions/6167515/simulating-finally-block-in-c0x – Nawaz May 29 '11 at 13:40
2

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.

2

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(...) { /**/ }
}
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • You forgot the `&` in `[]() { a = 2; }`. It should be `[&]() { a = 2; }` – Nawaz May 29 '11 at 10:26
  • @Nawaz: thanks for the catch... I am not used to those lambdas yet :/ – Matthieu M. May 29 '11 at 10:32
  • wow, i dont need the auto keyword for this solution. I like it! –  May 29 '11 at 10:47
  • @acidzombie24: the cheat is that `auto` is somewhat contained in `std::function`, since it can take both function pointers (or references) and lambdas :) I have added move semantics so that it may be returned from a function, if you so wish. – Matthieu M. May 29 '11 at 11:03
  • @Matthieu: See my second solution and let me know what you think. – Nawaz May 29 '11 at 11:16
  • btw do you remember this conversation where i didnt want to use default values set in the ctor? http://stackoverflow.com/questions/5921439/find-uninitialized-variables-in-cc/5921469#5921469 i solved it by using this in which i generated the classes including the checkMembers function http://stackoverflow.com/questions/6079052/property-like-features-in-c/6080497#6080497 –  May 29 '11 at 11:30
  • @Nawaz: checked, as well as the subsequent question, I still find using `std::function` neater. – Matthieu M. May 29 '11 at 15:28
  • @Matthieu: But that is not generic solution. If I'm not wrong, object of type `std::function` can hold address of function type `void (*)()` only , right? – Nawaz May 29 '11 at 15:31
1

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

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • 2
    This won't work if the impl doesn't elide the temporary. It would execute the dtor of the temporary in the return statement. – Johannes Schaub - litb May 29 '11 at 10:11
  • 1
    @Johannes: actually it does work. Its basically my code except MyFinally returns `D` instead of `D&`. I tested it in VS2010 –  May 29 '11 at 10:40
  • @acid I think you haven't understood my comment. Your compiler optimizes away the temporary and (in @Nawaz changed answer) the local variable. – Johannes Schaub - litb May 29 '11 at 10:58
  • @Johannes: ohh, i missed the 'if'. Btw i got that c++03 trick to work. Nawaz: He is saying IF the compiler implementation calls the dtor twice (once for the temporary, once after it returns) this wont work. –  May 29 '11 at 11:09
  • @acidzombie24 and @Johannes : See my second solution. Let me know your opinion. – Nawaz May 29 '11 at 11:15
  • I'm sorry it has the same problem. 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. – Johannes Schaub - litb May 29 '11 at 11:17
  • @Johannes: How exactly? How can `~lambda()` be called twice? – Nawaz May 29 '11 at 11:20
  • @Nawaz this will be too much to explain in a comment. If you want to know what happens exactly, I recommend to open a new question. – Johannes Schaub - litb May 29 '11 at 11:22
  • @Johannes: Actually the standard should be checked. They might specify if the variable should elide or not. Also do either of you know if virtual is REQUIRED by this trick? in my current code i literally use struct Dummy{} (and it seems fine) –  May 29 '11 at 11:24
  • @Johannes: Done. Please join it : http://stackoverflow.com/questions/6167515/simulating-finally-block-in-c0x – Nawaz May 29 '11 at 11:35
0

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));
}
Benjamin Lindley
  • 101,917
  • 9
  • 204
  • 274
  • unfortunately it is really less efficient :/ – Matthieu M. May 29 '11 at 10:16
  • @Matthieu: Compared to what? And why? – Benjamin Lindley May 29 '11 at 10:24
  • compared to the original solution. You allocate memory on the free store (which is never cheap) and you use `shared_ptr` which involves counter updates that are synchronized for multi-threading situations and thus involve broadcasting updates among cores to make sure the processors' cache are in sync. – Matthieu M. May 29 '11 at 10:31