为什么 std 类型不提供来自分配器不同源的转换构造函数/赋值

Why doesn't std types provide conversion constructor / assignment from sources differing in allocator

本文关键字:转换 构造函数 赋值 类型 std 为什么 分配器      更新时间:2023-10-16

例如,为什么template< typename Elem, typename Traits, typename Alloc > basic_string { ... }不提供:

template< typename OtherAlloc >
basic_string( const basic_string< Elem, Traits, OtherAlloc >& a_Other ) { ... }

实现这样一个尊重两个分配器的转换构造函数似乎相当微不足道。当前的事务状态使得在分配器中不同的类型之间进行接口非常麻烦。

标准哈希也不允许分配器乐趣 - 它可以哈希std::stringstd::wstring但不能std::basic_string<char, std::char_traits<char>, custom_alloc>。此外,您不能仅使用分配器创建unordered_mapunordered_set - 您还必须提供一个存储桶编号,该编号默认为无法访问的实现定义的常量,因此您实际上必须编造一些东西。一般来说,支持不是很好。

在我看来,相对简单,没有人提出这样的功能或探索这个使用空间。

这个问题比看起来要困难得多。由于分配器类型是对象类型的一部分,因此仅在分配器中不同的类型之间允许很少进行交互。我想到的第一个示例是,通过常量引用获取std::string的函数不能接受使用不同分配器的字符串。一种特殊的情况是在物体的构造过程中。事实上,这可能是这里更难的情况。

例如,请考虑以下代码:

// Assume that you could construct from a different allocator
std::vector<int, allocator1> f() {
   std::vector<int, allocator2> r;
   // fill in
   return r;
}
int main() {
   std::vector<int, allocator3> x = f();
}

考虑到allocator1std::allocator<int>的(即默认分配器),allocator2使用堆栈中的本地竞技场,并且allocator3可能使用共享内存。理论上,代码非常简单,向量r被创建并填充数据,在 return 语句上,通过从 r 复制创建一个新的临时,最后通过从该临时复制来构造x。问题在于该标准允许(和编译器喜欢)尽可能避免复制。在上面的特定示例中(并忽略分配器),编译器将省略两个副本并仅创建一次缓冲区,这既快速又高效。但是,由于分配器可能有所不同,因此必须禁用NRVO和其他类型的复制省略。(如果启用了这些优化,则 main 中的x将使用 allocator2 ,以及已被破坏的本地竞技场,从而导致未定义的行为)。

通过启用从一个分配器的容器到另一个分配器的复制构造,您最终可能会陷入混乱,或者比我们在当前标准中已经存在的更混乱,在这种情况下,您可以使用有状态分配器引起各种有趣的问题(假设您使用每线程分配器,并且将数据移动到共享队列中, 您最终可能会让一个线程在另一个线程上保存由每线程分配器创建的对象,并且由于使用每线程分配器的要点是避免对锁的争用,因此您可能会在看似安全的代码上创建争用条件。


这是向C++委员会提出的一个旧提案,旨在建立一个更好的分配模型,该模型提出了对C++03分配模型的一些担忧,并提出了一种多态分配器类型(它有自己的问题)。它读起来很有趣,但要注意细节,并非一切都像看起来那么好,使用任何一个选项(或类似于C++03版本的C++11版本)都存在很多陷阱。