In a project about a decade ago, we found that std::vector
's dynamic allocations caused a serious performance drain. In this case it was many small vectors allocated, so the quick solution was to write a vector-like class wrapping around a stack-based pre-allocated char
array used as the raw storage for its capacity. The result was a static_vector<typename T, std::size_t Max>
. Such a thing is easy enough to write if you know a few basics, and you can find quite a few such beasts on the web. In fact, boost has one, too, now.
Working on an embedded platform now, we happen to need a static_basic_string
. That would be a string that pre-allocates a fixed maximum amount of memory on the stack and uses that as its capacity.
At first I thought this should be fairly easy (it could be based it on the existing static_vector
, after all), but looking again at std::basic_string
's interface I am not so sure anymore. It is way more complex than std::vector
's interface. Especially implementing the family of find()
functions std::basic_string
comes with is more than just a tedious task.
That got me thinking again. After all, this is what allocators were created for: replace allocation based on new
and delete
with some other means. However, to say that the allocator interface is unwieldy would be an understatement. There are a few articles out there explaining it, but there is a reason I have seen so very few home-grown allocators in the last 15 years.
So here is my question:
If you had to implement a basic_string
lookalike, how would you do it?
static_basic_string
?std::basic_string
?As always, there is a rather essential restriction for us: Being on an embedded platform, we are tied to GCC 4.1.2, so we can only employ C++03, TR1, and boost 1.52.
In the case of strings being initialized via string literals (ie: "stack" ), it is allocated in a read-only portion of memory. The string itself should not be modified, as it will be stored in a read-only portion of memory.
The string object itself is stored on the stack but it points to memory that is on the heap.
Inside every std::string is a dynamically allocated array of char .
A string is generally considered as a data type and is often implemented as an array data structure of bytes (or words) that stores a sequence of elements, typically characters, using some character encoding. String may also denote more general arrays or other sequence (or list) data types and structures.
The first question is: how much of the extra interface do you
use? Most of std::string
's additional interfaces can be
trivially implemented using functions in <algorithm>
(e.g.
std::find
, std::find_if
and std::search
), and in a lot of
cases, there are large chunks of it that won't be used anyway.
Just implement it on an as needed basis.
I don't think you can make this work with a custom allocator. The only way to get the memory "on stack" would be to declare it as a member of the custom allocator, which would create all sorts of problems when copying them. And allocators must be copiable, and the copies must be idempotent.
Perhaps you can find a free implementation on the net of
std::string
which uses the small string implementation; then
modify it so that the cutoff size (beyond which it uses dynamic
allocation) is larger than any strings you actually use. (There
are several open-source implementations of the standard library
available; the one delivered with g++ still uses COW, but
I suspect that most of the others use SSO.)
It is easy, write a stack allocator, here's an example:
https://codereview.stackexchange.com/questions/31528/a-working-stack-allocator
With allocators, you can just as easily allocate, for example, from a memory-mapped file, i.e. from the disk drive, or from a static array of char
s.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With