Skip to content

think-cell 中强制静态局部变量仅存在一次

我们的计划通常需要资源,这些资源只能设置一次,并且只有在需要时才设置。常见的示例包括将大量数据加载到内存中的数据结构和初始化第三方库。这种情况在大型单体桌面和服务器应用程序中尤其频繁地发生,这些应用程序必须快速启动,并且可能会在需要许多此类资源之前终止。

C++ 编程语言提供的静态局部变量是解决这个问题的一个非常有吸引力的解决方案。它们在控件第一次通过其声明时被初始化,并且一旦初始化,它们的开销通常可以忽略不计,因为现代实现使用双重检查的锁定模式,只需要一个非原子字节比较来检查静态是否已经初始化。

危险

软件会随着时间的推移而发展。函数变长、拆分、内联,并且它们的签名会发生变化。大多数时候,代码演进不需要特别注意静态局部变量。但是,当包含这些变量的函数成为多次实例化的模板时,程序的语义和正确性可能会发生变化。即使函数仅使用单一类型调用,也经常使用 auto 参数的代码库特别容易受到此问题的影响。

这不仅仅是由于开发人员没有注意到静态局部变量造成的。发生这种情况也可能是因为代码不清楚这些变量的语义。

确保安全

记录这些变量在整个程序中应该只存在一次就像编写注释一样简单易行。然而,编译器不读取注释,许多程序员的行为就像编译器一样。编译器检查的解决方案总是比人为错误的解决方案更好。

将所有静态移动到命名空间作用域而不是将它们设为局部变量几乎不是一种选择,因为这会在进入 main 之前无条件地初始化所有静态变量。更糟糕的是,该程序可能会遭受静态初始化顺序的惨败 。

我们让编译器检查给定的静态是否在每个程序中最多实例化一次。这是通过一些有状态元编程来完成的,该元编程使用具有不同返回类型的朋友函数定义,如果包含我们的检测机制的函数被实例化两次,则触发错误。我们还定义了一个简短的 singleton_static 宏,该宏扩展到我们的检查,后跟 static 关键字,开发人员可以使用它不仅可以指示其静态局部变量的所需语义,还可以在编译时验证这些是否确实是单例。复制

template<bool const* p, typename SLOC>
struct assert_single_instantiation final {
	friend consteval std::integral_constant<bool const*, p> detect_multiple_instances(SLOC) {
        return {};
    }
};

#define ASSERT_SINGLE_INSTANTIATION \
	{ \
		static constexpr bool _b = false; \
		[](auto sloc) noexcept { \
			[[maybe_unused]] assert_single_instantiation<&_b, decltype(sloc)> _; \
		}(std::integral_constant<int, __COUNTER__>()); \
	}

#define singleton_static ASSERT_SINGLE_INSTANTIATION; static

如果您想知道,这不会以任何方式更改编译器生成的代码,正如您在编译器资源管理器中亲眼看到的那样。

让它变得更好

这个小工具的一个缺点是编译器错误说仅在返回类型上不同的函数不能重载。对于此用例来说,这不是一个很好的错误消息。是否可以使用允许您报告自定义错误消息的 static_assert 编写相同的检查?我们真的很想看到它。

Leave a Reply

Your email address will not be published. Required fields are marked *