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

4
理想情况下,一个不可变的字符串类只需要为每个字符串分配一次内存。甚至引用计数也可以存储在持有字符串本身的同一块内存中。
一个 stringshared_ptr 的简单实现会为 shared_ptr<string const> 分别分配三个不同的内存块:
  • 字符串缓冲区的内存
  • 字符串对象的内存
  • 引用计数的内存
我知道当使用 std::make_shared() 时,一个聪明的实现可以将后两者合并为单个分配。但这仍然会留下两个分配。
当你知道字符串是不可变的,字符串缓冲区不会被重新分配,因此应该可以将其与字符串对象集成,只留下一个分配。
我知道一些字符串实现已经对短字符串使用了这样的优化,但我希望有一种实现可以无论字符串长度如何都进行这种优化。
我的问题是:我的推理合理吗?是否允许实现这样的优化?我能否合理地期望优质标准库实现此优化?您是否知道当前的库实现可以实现此功能?
还是说这是我必须自己实现的东西?

GCC 4.x引入了引用计数的std::string:https://dev59.com/aGcs5IYBdhLWcg3w3HoG。如果使用`-D_GLIBCXX_USE_CXX11_ABI=0`编译,较新版本的GCC仍然具有此功能。 - John Zwinck
请查看allocate_shared - Caleth
@Caleth 那样做没有帮助,它的工作方式类似于 make_shared,只是使用了显式分配器。 - Some programmer dude
3
标准库中有一个引用计数的不可变字符串,它的拼写为std::runtime_error - T.C.
@T.C. 这让我差点大笑... :) 但是字符串仍然是单独分配的。所以仍然有两个分配:一个为对象,一个为字符串。 - Some programmer dude
显示剩余5条评论
2个回答

1
我相信唯一的方法是一个接受运行时变量大小数组的make_shared。标准库尚未支持,即使在C++17中(添加了shared_ptr对数组的支持)。
另一方面,Boost提供了boost::make_shared, 它可以带有一个数组大小参数。一旦你使用它,你就成功了;你得到一个shared_ptr<char[]>,它与你想要的很相似(除了实际上不是std::string)。
如果你不想使用Boost,你可以自己写。这可能并不难。
考虑到如果您只会创建O(1)字符串,那么更快的方法是永远不要删除它们,而是传递原始指针(或std :: string_view ),这样可以避免任何复制或引用计数调整。(引用计数实际上相当慢,因为它们使用原子操作。)您还可以使用像 std :: unordered_set 这样的内部机制。

如果使用C++17,您可以将boost::shared_ptr<char[]>封装在一个简单的类中,以便轻松访问std::string_view - aschepler
谢谢指出。不具有 std::string 的接口确实是一个明显的缺点。我考虑使用 std::string_view,但是如何获取数组的大小呢? - sh-
@sh- 嗯,看起来你实际上不能这样做。我猜如果你要这样做,你必须使用以 null 结尾的字符串。 - Dan
或者在字符串的前几个字节中手动存储长度。那时候,我可能会考虑自己实现整个功能。 - sh-
@Dan:我最终确实想删除这些字符串,一旦它们不再使用。 它们通常不短(通常约1k个字符),而且很难设计出一个单一的所有者。 但是,它们一旦构建就不会更改。 因此,共享所有权是首先考虑的事情。 - sh-

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;
    }
}

1
shared_ptr使用的分配器来分配字符串对象的内存,与string用于分配其存储空间的分配器不同,不是吗? - Dan
根据cppreference的说明,allocate_shared使用分配器的副本。 - Dan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接