【发布时间】:2021-06-14 22:58:13
【问题描述】:
我最近开始学习 C++,之前我用 Go 编程。
我最近被告知我不应该使用new,因为抛出的异常可能会导致分配的内存不是freed 并导致内存泄漏。一个流行的解决方案是 RAII,我找到了一个很好的解释为什么要使用 RAII 以及它是什么here。
但是,从 Go 开始,整个 RAII 事情似乎变得不必要地复杂。 Go 有一个叫做defer 的东西,它以一种非常直观的方式解决了这个问题。当范围以defer() 结尾时,您只需包装您想要做的事情,例如defer(free(ptr)) 或 defer(close_file(f)) 会在作用域结束时自动发生。
我进行了搜索,发现有两个来源试图在 C++ here 和 here 中实现延迟功能。两者最终都得到了几乎完全相同的代码,也许其中一个复制了另一个。他们在这里:
延迟实施 1:
template <typename F>
struct privDefer {
F f;
privDefer(F f) : f(f) {}
~privDefer() { f(); }
};
template <typename F>
privDefer<F> defer_func(F f) {
return privDefer<F>(f);
}
#define DEFER_1(x, y) x##y
#define DEFER_2(x, y) DEFER_1(x, y)
#define DEFER_3(x) DEFER_2(x, __COUNTER__)
#define defer(code) auto DEFER_3(_defer_) = defer_func([&](){code;})
延迟实施 2:
template <typename F>
struct ScopeExit {
ScopeExit(F f) : f(f) {}
~ScopeExit() { f(); }
F f;
};
template <typename F>
ScopeExit<F> MakeScopeExit(F f) {
return ScopeExit<F>(f);
};
#define SCOPE_EXIT(code) \
auto STRING_JOIN2(scope_exit_, __LINE__) = MakeScopeExit([=](){code;})
我有两个问题:
-
在我看来,这个
defer本质上与 RAII 做的事情相同,但更简洁、更直观。有什么区别,您是否发现使用这些defer实现有什么问题? -
我真的不明白
#define部分对上述这些实现的作用。两者有什么区别,哪一种更可取?
【问题讨论】:
-
我看不出这有多整洁。使用 RAII,我不需要告诉对象被推迟。它只是在超出范围时处理事情。除非这里没有说明有关延迟的其他内容。
-
@Alasdair - 哦,好的。与其“花费所有时间”使用析构函数编写类,不如为每个实例的每次使用编写代码。天哪,我想这样更好。你能告诉我当你想保护其中一个东西从一个范围传递到另一个的情况下它是如何工作的吗?因为如果 that 有效,那就太好了,因为您需要一直这样做!我更喜欢类/析构函数权衡自己:你不能忘记清理。
-
我相信
std::unique_ptr和标准库容器/函数会大大减少您需要编写的类的数量。如果它没有堆分配,你完全不用担心。 -
是的,那些完全相同的指针同时处理您用作示例的免费和封闭用例! (您可以指定
Deleter)。 -
如前所述,析构函数和 defer/finally 之间的区别在于,析构函数只需为每个类编写一次,编译器会使用该类为每个函数添加清理工作。