std::shared_ptr<std::string const> 可以作为引用计数的不可变字符串的有效实现吗?

Can std::shared_ptr<std::string const> serve as an efficient implementation of reference-counted immutable strings?

本文关键字:std 不可变 有效 实现 引用 字符串 lt ptr shared string const      更新时间:2023-10-16

理想情况下,不可变的字符串类只需要为每个字符串分配一个内存。甚至引用计数也可以存储在保存字符串本身的同一内存块中。

stringshared_ptr的简单实现将为shared_ptr<string const>分配三个不同的内存片段:

  • 字符串缓冲区的内存
  • 字符串对象的内存
  • 参考计数的内存

现在,我知道当使用std::make_shared()时,智能实现可以将最后两个合并到一个分配中。但这仍然会留下两个分配。

当您知道字符串是不可变的时,字符串缓冲区不会被重新分配,因此应该可以将其与字符串对象集成,只留下一个分配。

我知道一些字符串实现已经对短字符串使用了这样的优化,但我追求的是一个无论字符串长度如何都这样做的实现。

我的问题是:我的推理合理吗?实现是否真的允许并且能够做到这一点?我可以合理地期望一个高质量的标准库来实现这种优化吗?您知道这样做的当代库实现吗?

还是这是我必须自己实施的东西?

我相信唯一的方法是接受运行时变量大小数组的make_shared。标准版本没有,即使从 c++17 开始(它增加了对数组shared_ptr的支持)。

另一方面,Boost具有boost::make_shared,它也可以采用数组大小参数。一旦你拥有了它,你就是金色的;你会得到一个几乎可以做你想要的shared_ptr<char[]>(除了实际上是一个std::string

如果你不想使用boost,你可以自己动手。这可能不会那么难。

另外需要考虑的是,如果您只创建 O(1) 字符串,那么永远不删除它们并传递原始指针(或std::string_views)会快得多。这样可以避免任何复制或摆弄引用计数。(引用计数实际上非常慢,因为它们使用原子操作。

您也可以使用像std::unordered_set<std::string>这样的实习机制。

您可能需要为所有分配使用自定义分配器。

class ImmutableStringAllocator;
template<typename CharT>
using immutable_string = std::basic_string<CharT, std::char_traits<CharT>, ImmutableStringAllocator>
template<size_t N>
immutable_string<char> make_immutable_string(char (&data)[N])
{
ImmutableStringAllocator alloc(N);
// going for basic_string::basic_string(charT *, size_t, Allocator)
return allocate_shared<immutable_string<char>>(alloc, data, N, alloc);
}
class ImmutableStringAllocator {
size_t len;
size_t offset;
char * buf;
std::reference_wrapper<char *> ref;
public:
// Normal Allocator stuff here
ImmutableStringAllocator(size_t N) : len(N), buf(nullptr), offset(0), ref(buf) {}
ImmutableStringAllocator(const ImmutableStringAllocator & other) : len(other.len), buf(nullptr), offset(other.offset), ref(other.buf) {}
ImmutableStringAllocator operator=(const ImmutableStringAllocator & other) 
{ 
assert(buf == nullptr); 
temp(other); 
swap(*this, temp); 
return *this; 
}
pointer allocate(size_type n, const_void_pointer hint)
{
if (!ref.get()) { buf = ::new(n + len); offset = n; return buf; }
return ref.get() + offset;
}
}