std::function的Const正确性问题
const
类型限定符(type qualifier)是C++语言设计的一大亮点。我们围绕着这个语言特性使用“const
正确性” (const correctness)的实践来防止const
对象遭到改变。“const
正确性”的规则对大多数的类的实现都不难被遵守。但是对使用到类型擦除(type erasure)的类,“const
正确性”更难被遵守。不幸的是,因为短见,C++标准库中的std::function
类就成了一个“const
正确性”不被遵守的例子。
问题根源
std::function
的成员函数operator()
被标记为const
却可以改变内在的函数对象。比如说:
const std::function<int()> f {[x=0]() mutable { return ++x; }};
f(); // returns 1
f(); // returns 2
文档N43481第一次指出这个问题。并且直言
This violates not only basic tenets of
const-
correctness, but also the data race avoidance guarantees in [res.on.data.races]/p3, which states that "A C++ standard library function shall not directly or indirectly modify objects accessible by threads other than the current thread unless the objects are accessed directly or indirectly via the function's non-const
arguments, includingthis
".
大意是说operator()
的语义不仅仅违法了“const
正确性”基本原则,更破坏了标准库无数据争用(data race)的保证。
解决办法
类似于function
的类需要争对const
以及非const
有分别的模板特化(template specializations):
template<class Sig> class function; // not defined
template<class R, class... Args>
class function<R(Args...)>;
template<class R, class... Args>
class function<R(Args...) const>;
const
特化的operator()
应该被标记为const
,而它的构造函数不可以接受可变的函数对象。
function<int() const> f1 {[x=0]() { return x; }};
f1(); // 正常
function<int() const> f2 {[x=0]() mutable { return ++x; }}; // 无法编译
反过来说非const
特化的operator()
不会被标记为const
,所以你不能调用const
版本的function
:
function<int()> f1 {[x=0]() mutable { return ++x; }};
f1(); // 正常
const function<int()> f2 {[x=0]() mutable { return ++x; }};
f2(); // 无法编译
展望未来
我并不期望std::function
会做出破坏向后兼容的更改。截至本文发表时,我把希望放在已被提议的std::unique_function
2上。它不仅修复std::function
中“const
正确性”的bug,而且还带来了其他的改善。如果C++标准中有了std::function
替代品,我们就可以弃用(deprecate)std::function
如同当年的std::auto_ptr
。与此同时,我们也可以自己实现unique_function
,我的Github就有一个库实现了这个功能。