关于 c :为什么要调用 dtor(使用 annoymous/lambda func)

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

我正在尝试模仿最终喜欢的效果。所以我想我应该运行一个快速的脏测试。

这个想法是使用最重要的 const 来停止破坏并将 finally 块放在 lambda 中。但是显然我做错了什么,它在 MyFinally() 的末尾被调用。我该如何解决这个问题?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#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();
}

我的解决方案代码(注意:你不能在同一个块中有两个 finally。正如预期的那样。但仍然有点脏)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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);
}

很有趣,如果你删除


1
2
3
4
// 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); }

你可以通过引入移动构造函数来解决它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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();}
};

然后你可以重写你的玩具

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

希望对您有所帮助。当您使用 auto 时,不需要"常量引用"技巧。有关如何在 C 03 中使用 const 引用进行操作,请参见此处。


问题源于使用了函数生成器,正如 Johannes 所证明的那样。

我认为你可以通过使用另一个 C 0x 工具来避免这个问题,即 std::function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
};

那么,你可以简单地使用它:

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

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


您的代码和 Sutter 的代码不等价。他的函数返回一个值,你的函数返回一个对象的引用,该对象将在函数退出时被销毁。调用代码中的 const 引用不维护该对象的生命周期。


这个问题已经被其他人解释过了,所以我会建议一个修复方法,就像 Herb Sutter 写他的代码一样(顺便说一下,你的代码和他的不一样):

首先,不要通过 const 引用返回:

1
2
3
4
5
6
template<typename T>
D< T > MyFinally(T t)
{
   D< T > local(t); //create a local variable
   return local;
}

然后在调用点写这个:

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

这和 Herb Sutter 的代码一模一样。

演示:http://www.ideone.com/uSkhP

现在析构函数在退出 start() 函数之前被调用。

不再使用 auto 关键字的不同实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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; }
};

并将其用作:

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

看起来很有趣?

完整演示:http://www.ideone.com/DYqrh


你可以返回一个 shared_ptr:

1
2
3
4
template<typename T>
std::shared_ptr<D< T >> MyFinally(T t) {
    return std::shared_ptr<D< T >>(new D< T >(t));
}