寻找 C++ 技术的名称:确保通过“终结器对象”释放资源

问题描述 投票:0回答:1

设定

有时,即使在编写具有 RAII 优点的现代 C++ 时,您也需要手动管理资源。我想知道我通常使用的技术是否有名称。

资源管理很乏味,但也许你正在调用一个旧的图书馆或其他什么。现在我假设有一个函数

acquire()
需要在某些函数的开头调用,如果这样做,你必须确保very在函数退出时,对应的
release()
被称为。

手动执行此操作意味着必须确保 (a) 函数中每个

release()
之前都有一个
return
(或
void
函数的函数结尾),(b) 没有例外可以在不发生任何情况下转义该函数
release()
被调用,并且 (c) 该函数的每个未来维护者都知道并维护这一点。这已经是一场灾难了。 :)

技术

相反,我喜欢做的是创建一个“终结器对象”,我称之为

acquire()
。为此,我创建了一个
Finally
类模板,大致如下:

template<class Func>
class Finally {
public:

  Finally(Func f) : storedFunc(f) {}

  ~Finally() { storedFunc(); }

private:
  Func storedFunc;
};

(暂时忽略异常处理、有效地将函数移至

storedFunc
等)

然后使用如下:

void someFunction() {
    …
    acquire();
    Finally finalizer{[](){ release(); }};
    …
}

重要的是这两条线紧接着彼此。这样,当

someFunction
结束时(无论为什么......),本地
finalizer
对象将被销毁,并在存储的 lambda 中调用
release()

这项技术有既定的名称吗?

讨论

我还没有真正看到这个在很多地方被使用。我不知道这是为什么。我当然知道这种技术的一些缺点:

  • 如果传入的 lambda 中出现任何异常,情况就会变糟™。在实践中,人们可能希望在
    catch(...)
    中出现
    ~Finally()
    (带有一些亮红色的错误日志记录),具体取决于“程序崩溃”或“程序泄漏资源”是否被认为更糟糕。
  • 如果
    Finally()
    的构造函数抛出异常,则资源被泄漏。我们只是确保它不会。 :)
  • 对于非常注重性能的人:只要 lambda 不变得疯狂复杂,整个恶作剧就会从
    -O1
    开始完全优化(参见 at Godbolt

是否还有其他我遗漏的重大陷阱,导致这是一个坏主意?

c++ c++17 resources idioms
1个回答
0
投票

您描述的技术通常称为“资源获取即初始化(RAII)”。 RAII 是一种 C++ 编程习惯,其中资源的生命周期与对象的范围相关。当对象(在本例中为 Final 对象)超出范围时,会自动调用其析构函数,从而允许您释放获取的资源。 在您的示例中,

Finally

类中的 lambda 函数充当清理操作(释放资源),并在Finally对象在作用域末尾被销毁时自动调用。 以下是一些注意事项和潜在的陷阱:

异常安全:

如果Finally内部的lambda在执行过程中抛出异常,可能会导致意外的行为,特别是在需要执行其他清理操作的情况下。正如您提到的,添加 catch 块来记录错误是一个很好的做法。

初始化顺序:

请谨慎对待具有此模式的对象的构造和销毁顺序。如果您有多个具有清理操作的对象,那么它们的销毁顺序很重要。

资源泄漏的可能性:

正如你提到的,如果Finally的构造函数抛出异常,则资源可能会泄漏。确保构造函数不抛出异常对于此模式的可靠性至关重要。

性能:

虽然性能开销通常很小并且可以通过编译器进行优化,但仍然需要注意,特别是在性能关键型代码中。 总体而言,RAII 是 C++ 中用于资源管理的强大且广泛使用的习惯用法,并且您对它的使用与 RAII 的原则非常一致。请注意特定用例中的异常安全和对象生命周期注意事项。

© www.soinside.com 2019 - 2024. All rights reserved.