场景
在编程中,错误处理是经常遇到的。而在遇到错误时,恢复已处理数据或释放已分配资源也是不可避免的。而经常使用的方法无非几种,1)在每个错误处理的返回前处理;2)goto到最后统一处理再返回;3)抛出异常在catch中处理。几种方法,都有各自的优点和缺点。对比如下
实现
可见,几种常见方法各有优劣。那有没有一种使用方法,没有明显缺点的方法呢?这个在Golang中有一个方法:defer。Defer指定的语句或函数,会在超出其所在作用域(多数用于函数返回时)执行,可以做些清理收尾工作。
那在C++中能不能实现这样功能呢?答案是肯定的。RAII的理念,就是很好的。把收尾的工作,当作是资源的释放,让这一切在开始的时候就保证发生。基于这个理论,可以这样做:
void fun(bool doit){// something to initalize;std::shared_ptr defer(nullptr, [](void*){// uninitalize or recovery;});if (!doit){return;}// 其他的一些处理return;}
像这样,我们就有机会在defer的deleter中去做一些我们希望的事情。一般来说,从实现目的上来说我们可以这样做,但是从代码质量上来说,这并不是好的代码,在大家的共识中,std::shared_ptr是进行资源管理的,当某一天再次看到这段代码或其他没有这方面认知的程序员看到这段代码,可能就觉得莫名其妙了。
从提高代码质量的角度,我们可以创建defer类来做这件事情。这个defer类大约像这个样子
class defer{using OnDestructorAction = std::function;public:defer(OnDestructorAction action) : OnDestructor(action) {}~defer() { OnDestructor(); }protected:OnDestructorAction OnDestructor;};
刚才的函数使用这个类,可以这样改
void fun(bool doit){// something to initalize;defer clean([](){// uninitalize or recovery;});if (!doit){return;}// 其他的一些处理return;}
现在,当我们再次看到时候,就会知道,这里是延时操作(defer),要清理什么(clean)东东,并且任何有C++经验的程序员看到defer的实现都能知道这段代码的工作原理。
但这里依然存在问题,可能会这样创建一个对象 defer clean2(clean); 此时,指定的函数将会被执行两次——clean析构时候执行一次,clean2析构的时候又执行一次——大多时候这不是我们期望的。我们需要禁止defer对象被复制——禁用拷贝构造函数和赋值。完成后如下
class defer{using OnDestructorAction = std::function;public:defer(defer&) = delete;defer(OnDestructorAction action) : OnDestructor(action) {}~defer() { OnDestructor(); }defer operator=(defer&) = delete;protected:OnDestructorAction OnDestructor;};
这样,当有人试图复制对象时将不被允许。这个简单的类又完善了一步。但是,作为一个辅助延时执行的类,不是应该被创建在堆上的——因为这样此对象本身就需要被手动管理——我们自己可以不这样使用,但是不能保证团队中每个人都能正确使用。所以不希望对他操作的,就要声明禁止操作。如何禁止在堆上创建对象呢?当然是new一个了。更改后,大约像这个样子
class defer{using OnDestructorAction = std::function;public:defer(defer&) = delete;defer(OnDestructorAction action) : OnDestructor(action) {}~defer() { OnDestructor(); }defer operator=(defer&) = delete;void* operator new(size_t) = delete;protected:OnDestructorAction OnDestructor;};
通过重载并显式地删除new操作符,现在我们将不能使用new在堆上创建此对象。当new defer时,将得到编译器给出的"error C2280:尝试引用已删除的函数"的错误提示。
到现在,这个类已经能保证在大多数情况下被以正确的方法使用了。然而,依然可以通过别的方法在堆上创建此类的对象,但我却无能为力:
defer* pdefer = (defer*)malloc(sizeof(defer));pdefer->defer::defer([]() {});
当然,如果有人非要以这种方式来创建,那我会认为他是故意的。
至此,实现了类似golang的defer功能!来一段简单但完整的测试程序
#include #include class defer{using OnDestructorAction = std::function;public:defer(defer&) = delete;defer(OnDestructorAction action) : OnDestructor(action) {}~defer() { OnDestructor(); }defer operator=(defer&) = delete;void* operator new(size_t) = delete;protected:OnDestructorAction OnDestructor;};void fun(bool doit){defer clean([](){std::cout << "clean" << std::endl;});if (!doit){std::cout << "! doit" << std::endl;return;}defer clean2([](){std::cout << "clean 2" << std::endl;});std::cout << "doit" << std::endl;return;}int main(){std::cout << "fun(false)" << std::endl;fun(false);std::cout << "func(true)" << std::endl;fun(true);return 0;}
执行以上程序,将得到如下输出
fun(false)! doitcleanfunc(true)doitclean 2clean
原创文章,转载请保留出处 https://www.toutiao.com/i6894983289799606787/