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)); } |
希望对您有所帮助。当您使用
问题源于使用了函数生成器,正如 Johannes 所证明的那样。
我认为你可以通过使用另一个 C 0x 工具来避免这个问题,即
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
现在析构函数在退出
不再使用 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)); } |