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

时间:2014-10-14 08:47:32

标签: c++ string allocator stack-allocation

在大约十年前的一个项目中,我们发现std::vector的动态分配导致了严重的性能损失。在这种情况下,它分配了许多小向量,因此快速解决方案是编写一个类似于向量的类,围绕基于堆栈的预分配char数组,该数组用作其容量的原始存储。结果是static_vector<typename T, std::size_t Max>。如果您了解一些基础知识,这样的事情很容易写,你可以找到quite a few这样的野兽on the web。事实上,boost has one现在也是。

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

起初我认为这应该相当简单(毕竟它可能基于现有的static_vector),但再次查看std::basic_string的界面我并非如此肯定了。它比std::vector的界面更复杂。特别是实现find()函数std::basic_string系列不仅仅是一项繁琐的工作。

这让我再次思考。毕竟,这就是创建分配器的方法:使用其他方法替换基于newdelete的分配。但是,要说分配器接口不实用将是轻描淡写。有一些文章在那里解释它,但是有一个原因我在过去的15年中看到了很少本土的分配器。

所以这是我的问题:

如果你必须实现basic_string相似,你会怎么做?

  • 自己编写static_basic_string
  • 编写一个分配器以传递给std::basic_string
  • 做一些我没想到的事情?
  • 使用来自提升的东西我不知道?

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

7 个答案:

答案 0 :(得分:4)

第一个问题是:你有多少额外的界面 使用?大多数std::string的附加接口都可以 使用<algorithm>中的函数轻松实现(例如 std::findstd::find_ifstd::search),以及很多 但是,有很多大块的东西无论如何都不会被使用。 只需根据需要实施它。

我不认为你可以使用自定义分配器来完成这项工作。 在堆栈上获取内存的唯一方法是#34;是宣布它 作为自定义分配器的成员,它将创建所有 复制它们时遇到的各种问题。分配器必须是 可复制的,副本必须是幂等的。

也许你可以在网上找到一个免费的实现 std::string使用小字符串实现;然后 修改它以使截止大小(超过它使用动态 allocation)比你实际使用的任何字符串都大。 (那里 是标准库的几个开源实现 可用的;用g ++提供的那个仍然使用COW,但是 我怀疑其他大多数人都使用SSO。)

答案 1 :(得分:1)

很容易,写一个堆栈分配器,这是一个例子:

https://codereview.stackexchange.com/questions/31528/a-working-stack-allocator

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

答案 2 :(得分:1)

有很多basic_string实现,一些完全基于动态分配,另一些仅针对比给定长度宽的字符串进行动态分配(实际上,它们在适合时使用自己的内部缓冲区)。 / p>

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

在堆栈上实现字符串时的问题是你不能让它动态增长(可能是当时的堆栈有更多的东西)但是你还必须处理像+=那样可以创建的操作字符串越来越长。

所以你最终通过预分配(作为一个数组或一个alloca提供的缓冲区,在你的类中或在一个分配器中不会改变问题)你将大部分浪费的字节数但是没有全部填充它们,或者如果字符串增长太多并且需要动态,则不使用它们。

对于内存到缓存通信过程(通常运行128个字节或4个字节)可能需要权衡取舍,但它依赖于硬件,因此提供的复杂性不太可能为此付出代价。 / p>

更实惠的解决方案可以是仍然在堆上分配的分配器,但是能够保留并重用返回的块(达到一定限制),从而减少了要求系统分配/解除分配的需要。

但是,如果底层系统已经以这种方式实现new/delete,那么在这种情况下,性能可能不一定有益。

答案 3 :(得分:1)

LLVM ADT具有SmallString类。它还有SmallVector和许多其他有用的类。

虽然当前的LLVM代码库正朝着使用C ++ 11的方向发展,但(不是那么)老版本的LLVM支持C ++ 03。

答案 4 :(得分:1)

一个很好的起点是Alexandrescu's policy-based string class, described in this Dr Dobbs article。它包含一个SSO策略,基本上可以执行您想要的操作(搜索SmallStringOpt的页面),并且在您认为必要时可以轻松修改。它早于C ++ 11,所以你也很好。

答案 5 :(得分:0)

我应该考虑使用实现定义的VLA和标准算法的组合。

答案 6 :(得分:-1)

这是一个有效的代码,但不推荐的方式

此代码有很多痕迹可以显示它正在做什么。它不会检查分配请求的大小是否超过缓冲区。如有必要,您可以进行检查。请注意,std :: basic_string尝试分配超过必要的值。

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

此代码已在GCC和LLVM上测试,并按预期(或意外)执行。

笨拙的语法。无法从basic_string派生并嵌入缓冲区。更好的方法是使用basic_string接口的必需子集创建一个小的专用buffer_string类。