C++ 如何实现仅在堆栈上分配的字符串

C++ 如何实现仅在堆栈上分配的字符串,c++,string,allocator,stack-allocation,C++,String,Allocator,Stack Allocation,在大约十年前的一个项目中,我们发现std::vector的动态分配造成了严重的性能损失。在本例中,分配了许多小向量,因此快速解决方案是编写一个类似向量的类,围绕一个基于堆栈的预分配char数组,用作其容量的原始存储。结果是一个静态向量。如果你知道一些基本知识,写这样的东西很容易,而且你可以找到这样的野兽。事实上,现在也是 现在在嵌入式平台上工作,我们恰好需要一个静态\u basic\u字符串。这将是一个字符串,它在堆栈上预先分配固定的最大内存量,并将其用作其容量 起初我认为这应该是相当容易的(毕

在大约十年前的一个项目中,我们发现
std::vector
的动态分配造成了严重的性能损失。在本例中,分配了许多小向量,因此快速解决方案是编写一个类似向量的类,围绕一个基于堆栈的预分配
char
数组,用作其容量的原始存储。结果是一个
静态向量
。如果你知道一些基本知识,写这样的东西很容易,而且你可以找到这样的野兽。事实上,现在也是

现在在嵌入式平台上工作,我们恰好需要一个
静态\u basic\u字符串
。这将是一个字符串,它在堆栈上预先分配固定的最大内存量,并将其用作其容量

起初我认为这应该是相当容易的(毕竟它可以基于现有的
静态向量
),但再看看
std::basic_string
的接口,我就不那么确定了。它比
std::vector
的接口复杂得多。尤其是实现
find()
函数系列
std::basic_string
带来的不仅仅是一项乏味的任务

这让我再次思考。毕竟,这就是创建分配器的目的:用其他方法替换基于
new
delete
的分配。然而,如果说分配器接口笨拙,那就太轻描淡写了。有几篇文章对此进行了解释,但有一个原因,我在过去15年中看到的本土分配器非常少

所以我的问题是:

如果您必须实现一个相像的
basic\u字符串,您将如何实现它

  • 编写自己的
    静态\u基本\u字符串
  • 编写一个分配器以传递给
    std::basic_string
  • 做我没想到的事
  • 使用boost中我不知道的东西

和往常一样,我们有一个相当重要的限制:在嵌入式平台上,我们与GCC4.1.2绑定,因此我们只能使用C++03、TR1和boost 1.52

编写堆栈分配器很简单,下面是一个示例:


使用分配器,您可以同样轻松地进行分配,例如,从内存映射文件(即磁盘驱动器)或从
char
s的静态数组进行分配。

有许多
基本字符串
实现,有些完全基于动态分配,另一些则只对比给定长度更宽的字符串进行动态分配(事实上,它们在合适的时候使用自己的内部缓冲区)

使用分配器可能不是最好的方法,因为字符串和分配器之间的接口假定分配器对象是容器的一部分,但分配的内存来自容器本身的外部。您可以通过使用POSIX
alloca
实现一个分配器来安排它,并使用所有

在堆栈上实现字符串时的问题是,您不能让它动态增长(可能是当时的堆栈有更多的内容),但您还必须注意像
+=
这样的操作,这些操作会使字符串越来越长

因此,您最终通过预分配(作为数组或alloca提供的缓冲区,在您的类中或在分配器中,不会改变问题)大量的字节来结束,这些字节通常会被浪费,但不会全部填充,或者如果字符串增长过多并且需要动态,则不使用它们

内存到缓存通信过程(通常使用128字节或4KB运行)可能有一个折衷方案,但它强烈依赖于硬件,因此负担得起的复杂性很可能不会带来任何代价

更经济的解决方案可以是分配器,它仍然在堆上分配,但能够保留和重用返回的块(达到一定的限制),从而减少要求系统分配/取消分配的需要

但是,在这种情况下,如果底层系统已经以这种方式实现了
new/delete
,那么性能可能不一定会受益。它还有
SmallVector
和许多其他有用的类


当当前的LLVM代码库转向使用C++11时,(并非如此)旧版本的LLVM支持C++03。

我想我会结合使用实现定义的VLA和标准算法。

第一个问题是:您需要多少额外的接口 使用?大多数
std::string
的附加接口都可以 使用
中的函数实现(例如。
std::find
std::find_if
std::search
),以及大量 在某些情况下,它的大部分都不会被使用。 只要在需要的基础上实施它

我认为使用自定义分配器无法实现这一点。 获取“堆栈上”内存的唯一方法是声明它 作为自定义分配器的成员,该分配器将创建所有 复制时会出现各种问题。并且分配器必须是 可复制,并且副本必须是幂等的

也许你可以在互联网上找到一个免费的实现
std::string
使用小字符串实现;然后 对其进行修改,使截断大小(超过该大小时,将使用动态 分配)大于实际使用的任何字符串。(那里 是标准库的几种开源实现 可用;与g++一起提供的版本仍然使用COW,但是
我怀疑大多数其他人都使用SSO。)

一个很好的起点是。它包括一个SSO策略,基本上可以满足您的需要(在页面中搜索
SmallStringOpt
),并且在您认为必要时可以轻松修改。它早于C++11,所以您在那里也很好。

这是一个工作代码,但不是推荐的方法

这段代码有很多跟踪来显示它在做什么。是的
#include <string>
#include <iostream>

template<typename T, size_t S>
class fixed_allocator
{
  typedef std::allocator<T> _base;

  std::ostream& trace() const { return std::cerr << "TRACE fixed_allocator " << (void*)this ; }

public:
  typedef typename _base::value_type value_type;
  typedef typename _base::pointer pointer;
  typedef typename _base::const_pointer const_pointer;
  typedef typename _base::reference reference;
  typedef typename _base::const_reference const_reference;
  typedef typename _base::size_type size_type;
  typedef typename _base::difference_type difference_type;

  template<class C> struct rebind {
    typedef fixed_allocator<C, S*sizeof(C)/sizeof(T)> other;
  };
  T* buffer_;

  fixed_allocator(T* b) : buffer_(b) { trace() << "ctor: p="  << (void*)b << std::endl; }

  fixed_allocator() : buffer_(0) { trace() << "ctor: NULL" << std::endl; };
  fixed_allocator(const fixed_allocator &that) : buffer_(that.buffer_) { trace() << "ctor: copy " << (void*)buffer_ << " from " << (void*) &that << std::endl; };

  pointer allocate(size_type n, std::allocator<void>::const_pointer hint=0) {
    trace() << "allocating on stack " << n << " bytes" << std::endl;
    return buffer_;
  }

  bool operator==(const fixed_allocator& that) const { return that.buffer_ == buffer_; }
  void deallocate(pointer p, size_type n) {/*do nothing*/}
  size_type max_size() const throw() { return S; }
};

int main()
{
  char buffer_[256];
  fixed_allocator<char, 256> ator(buffer_);
  std::basic_string<char, std::char_traits<char>, fixed_allocator<char, 256> > str(ator);
  str.assign("ipsum lorem");
  std::cout << "String: '" << str << "' length " << str.size() << std::endl;
  std::cout << " has 'l' at " << str.find("l") << std::endl;
  str.append(" dolor sit amet");
  std::cout << "String: '" << str << "' length " << str.size() << std::endl;
  str.insert(0, "I say, ");
  std::cout << "String: '" << str << "' length " << str.size() << std::endl;
  str.insert(7, "again and again and again, ");
  std::cout << "String: '" << str << "' length " << str.size() << std::endl;
  str.append(": again and again and again, ");
  std::cout << "String: '" << str << "' length " << str.size() << std::endl;
  return 0;
}