【问题标题】:Can std::shared_ptr<std::string const> serve as an efficient implementation of reference-counted immutable strings?std::shared_ptr<std::string const> 可以作为引用计数不可变字符串的有效实现吗?
【发布时间】:2017-06-29 11:17:31
【问题描述】:

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

stringshared_ptr 的简单实现将为 shared_ptr&lt;string const&gt; 分配三个不同的内存:

  • 字符串缓冲区的内存
  • 字符串对象的内存
  • 引用计数的内存

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

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

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

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

或者这是我必须自己实现的东西?

【问题讨论】:

  • GCC 4.x 引用计数 std::string: stackoverflow.com/questions/12520192/… 。如果您使用-D_GLIBCXX_USE_CXX11_ABI=0 编译,更高版本的 GCC 仍然有它。
  • @Caleth 这无济于事,它的工作原理与make_shared 很相似,只是它使用了显式分配器。
  • 标准库中有一个引用计数的不可变字符串。拼写为std::runtime_error
  • @T.C.这让我几乎哈哈大笑... :) 但是字符串仍然是 separatley 分配的。所以仍然有两种分配:一种用于对象,一种用于字符串。

标签: c++ string immutability c++-standard-library


【解决方案1】:

我相信这样做的唯一方法是make_shared,它接受运行时可变大小的数组。标准的 does not,甚至从 c++17 开始(它将对 shared_ptr 的支持添加到数组中)。

另一方面,Boost 有boost::make_shared,它也可以采用数组大小​​参数。一旦你有了它,你就是金子;你会得到一个shared_ptr&lt;char[]&gt;,它几乎可以满足你的需求(除了实际上是std::string

如果您不想使用 boost,您可以自己滚动。应该不会那么难。

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

你也可以使用像std::unordered_set&lt;std::string&gt;这样的实习机制。

【讨论】:

  • 如果使用 C++17,您可以将 boost::shared_ptr&lt;char[]&gt; 包装在一个简单的类中,这样您就可以轻松访问 std::string_view
  • 感谢您指出这一点。然而,没有 std::string 的接口是一个明显的缺点。我考虑过使用 std::string_view,但我如何实际获得数组的大小?
  • @sh- 嗯,看起来你实际上不能。我想如果你这样做的话,你将不得不使用以 null 结尾的字符串。
  • 或者在字符串的前几个字节中手动存储长度。到那时我还不如自己实现整个事情。
  • @Dan:一旦不再使用这些字符串,我确实想最终删除它们。它们通常不短(通常大约 1k 个字符),并且设计单个所有者并不容易。但是,一旦构建,它们就不会改变。所以首先想到的是共享所有权。
【解决方案2】:

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

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

【讨论】:

  • shared_ptr用来分配字符串对象的分配器和字符串用来分配存储的分配器不是不同吗?
  • 根据 cppreference,allocate_shared 使用分配器的副本。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-11-10
  • 1970-01-01
  • 2019-10-20
  • 1970-01-01
  • 1970-01-01
  • 2020-08-20
  • 1970-01-01
相关资源
最近更新 更多