patterncppMinor
Stack buffer class
Viewed 0 times
bufferclassstack
Problem
This is a class for storing memory on the stack when possible or in
Note: Using
std::vector when it grows too much.Note: Using
std::vector with custom stack allocator would be arguably better but I did not want to depend on 3rd party allocators (and could not find one for C++11 that compiles cleanly).#include
#include
#include
#include
template
class stack_buf
{
public:
using bufpair_t = std::pair;
stack_buf() :_v(), _stack_array(), _stack_size(0) {}
~stack_buf() {};
stack_buf& operator=(const stack_buf& other) = delete;
stack_buf(const stack_buf& other)
{
_stack_size = other._stack_size;
if (!other._v.empty())
_v = other._v;
else if (_stack_size)
std::copy(other._stack_array.begin(), other._stack_array.begin() + _stack_size, _stack_array.begin());
}
stack_buf(stack_buf&& other)
{
_stack_size = other._stack_size;
if (!other._v.empty())
_v = std::move(other._v);
else if (_stack_size)
std::copy(other._stack_array.begin(), other._stack_array.begin() + _stack_size, _stack_array.begin());
other.clear();
}
void append(const char* buf, std::size_t buf_size)
{
//If we are aleady using _v, forget about the stack
if (!_v.empty())
{
_v.insert(_v.end(), buf, buf + buf_size);
}
//Try use the stack
else
{
if (_stack_size + buf_size _v;
std::array _stack_array;
std::size_t _stack_size;
};Solution
- Leading underscores
As Jamal pointed out in the comments, using a leading
_ is dangerous, since it easily uses a reserved identifier (for example, when followed by a capital letter). I think the leading underscores are not a problem here, but I'd advise against using them. Some people use trailing underscores for data members for this reason.- Includes
#include
#include
#include // or ; for std::size_t
#include I sorted them alphabetically (thanks, Morwenn) so you can see if you include them multiple times.
- Template parameters
template
class stack_bufI don't see any reason why should constrain this class to
char. It works perfectly fine for other types, and doesn't use any string assumptions/functionality.- Publishing information
public:
using value_type = T;
using iterator = T const*;
using bufpair_t = std::pair;
static constexpr std::size_t stack_size = N;I think it's typically a good idea to publish the information about the type of your class, since you can infer this via partial specialization anyway. The
iterator typedef increases compatibility with StdLib container classes.- Reducing code duplication
The copy constructor and move constructor are almost equivalent. With some function templates, we can reduce the code duplication:
private:
struct delegate_copy_move{};
template
stack_buf(T1&& other, delegate_copy_move)
: _stack_size( other._stack_size )
{
if (other.uses_vector())
_v = std::forward(other)._v;
else
std::copy_n(other._stack_array.begin(), _stack_size, _stack_array.begin());
}
bool uses_vector() const
{
return ! _v.empty();
}
public:
stack_buf(stack_buf const& other)
: stack_buf(other, delegate_copy_move{})
{}
stack_buf(stack_buf&& other)
: stack_buf(std::move(other), delegate_copy_move{})
{
other.clear();
}Note that I also replaced
std::copy with std::copy_n, and removed the redundant check if (_stack_size): both algorithms work correctly with empty ranges. For arbitrary types, we should replace the copy_n with a move, dependent on whether T1 is an lvalue ref (something like a forward_n).The
uses_vector() helper function IMO increases readability, and becomes more important in an optimization (see below).- Default constructor and destructor
stack_buf() : _stack_size(0) {}
~stack_buf() = default;I'd advise against explicitly initializing the
_stack_array data member. This will perform value-initialization, which zeroes the array. The user of the class then doesn't have any way to create an uninitialized stack_buf. However, by leaving out the value-init, the user still can write stack_buf<> s{}; or auto s = stack_buf<>{}; for value-initialization.The destructor should be defaulted or left out completely.
- The
appendfunction
void append(value_type const* buf, std::size_t buf_size)
{
if (uses_vector())
{
_v.insert(_v.end(), buf, buf + buf_size);
}
else
{
if (_stack_size + buf_size <= stack_size)
{
std::copy_n(buf, buf_size, _stack_array.begin() + _stack_size);
_stack_size += buf_size;
}
//Not enough stack space. Copy all to _v
else
{
_v.reserve(_stack_size + buf_size);
try
{
_v.insert(_v.end(), _stack_array.begin(),
_stack_array.begin() + _stack_size);
_v.insert(_v.end(), buf, buf + buf_size);
}catch(...)
{
_v.clear();
throw;
}
}
}
}I replaced the
std::memcpy with a std::copy_n. As far as I know, there's no reason to use use std::memcpy at all. Again, I removed the redundant check if(_stack_size).With the try-catch-clause, the
append function now doesn't "lose" any data: When an exception occurs, we still have the data in the stack array, but the user cannot access it. By clearing the vector, we restore access to this data.Instead of the try-catch-clause, you could also introduce a temporary vector (see below).
For arbitrary element types, we should probably use
move_if_noexcept to move the elements from the array to the vector. If this is successful, we should destroy the elements in the stack array (since they might have been copied). This requires replacing the copy_n with some placement-new calls.- Accessors
```
bufpair_t get() const
{
if (uses_vector())
return bufpair_t(_v.data(), _v.size());
else
return bufpair_t(_stack_array.data(), _stack_size);
}
iterator begin() const
{
return get().first;
}
iterator end() const
{
re
Code Snippets
#include <algorithm>
#include <array>
#include <cstddef> // or <cstring>; for std::size_t
#include <vector>template<typename T = char, std::size_t N = 50>
class stack_bufpublic:
using value_type = T;
using iterator = T const*;
using bufpair_t = std::pair<iterator, std::size_t>;
static constexpr std::size_t stack_size = N;private:
struct delegate_copy_move{};
template<class T1>
stack_buf(T1&& other, delegate_copy_move)
: _stack_size( other._stack_size )
{
if (other.uses_vector())
_v = std::forward<T1>(other)._v;
else
std::copy_n(other._stack_array.begin(), _stack_size, _stack_array.begin());
}
bool uses_vector() const
{
return ! _v.empty();
}
public:
stack_buf(stack_buf const& other)
: stack_buf(other, delegate_copy_move{})
{}
stack_buf(stack_buf&& other)
: stack_buf(std::move(other), delegate_copy_move{})
{
other.clear();
}stack_buf() : _stack_size(0) {}
~stack_buf() = default;Context
StackExchange Code Review Q#49340, answer score: 5
Revisions (0)
No revisions yet.