理想情况下,不可变的字符串类只需要为每个字符串分配一个内存。甚至引用计数也可以存储在保存字符串本身的同一块内存中。
string
和shared_ptr
的简单实现会为shared_ptr<string const>
分配三个不同的内存:
现在,我知道在使用std::make_shared()
时,智能实现可以将最后两个组合成一个分配。但这仍然会留下两个分配。
当你知道字符串是不可变的时,字符串缓冲区将不会被重新分配,因此应该可以将它与字符串对象集成,只留下一个分配。
我知道一些字符串实现已经对短字符串使用了这样的优化,但我正在执行一个实现,无论字符串长度如何。
我的问题是:我的推理是否合理?实现是否允许并且能够执行此操作?我可以合理地期望从高质量的标准库中实现这种优化吗?你知道当代图书馆的实施吗?
或者这是我必须自己实施的东西吗?
答案 0 :(得分:1)
我认为唯一的方法是make_shared
接受运行时变量大小的数组。标准的does not,即使是c ++ 17(它为数组增加了对shared_ptr
的支持)。
Boost具有boost::make_shared
,它也可以采用数组大小参数。一旦你拥有了,你就会变得金黄;你得到的shared_ptr<char[]>
几乎可以满足您的需求(除了实际上是std::string
。
如果你不想使用提升,你可以自己动手。它可能不会那么难。
要考虑的其他事情是,如果你只创建O(1)字符串,那么永远不会删除它们并传递原始指针(或std::string_view
s)会快得多。这可以避免任何复制或摆弄引用计数。 (引用计数实际上非常慢,因为它们使用原子操作。)
您还可以使用std::unordered_set<std::string>
等实习机制。
答案 1 :(得分:0)
您可能需要为所有分配使用自定义分配器。
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;
}
}