Separate memory management and formatting

Array is split into an abstract Buffer class and a concrete MemoryBuffer class. BasicWriter now does all memory allocation through a Buffer object. Subclasses of BasicWriter may use different buffer types. The new BasicMemoryBuffer class uses the default MemoryBuffer.
This commit is contained in:
Victor Zverovich
2014-09-29 08:48:16 -07:00
parent 5ca3d00e26
commit d1ded569ff
11 changed files with 596 additions and 498 deletions
+162 -129
View File
@@ -123,7 +123,7 @@ FMT_GCC_EXTENSION typedef unsigned long long ULongLong;
using std::move;
#endif
template <typename Char, typename Allocator = std::allocator<Char> >
template <typename Char>
class BasicWriter;
typedef BasicWriter<char> Writer;
@@ -222,8 +222,8 @@ public:
namespace internal {
// The number of characters to store in the Array object, representing the
// output buffer, itself to avoid dynamic memory allocation.
// The number of characters to store in the MemoryBuffer object itself
// to avoid dynamic memory allocation.
enum { INLINE_BUFFER_SIZE = 500 };
#if _SECURE_SCL
@@ -237,72 +237,32 @@ template <typename T>
inline T *make_ptr(T *ptr, std::size_t) { return ptr; }
#endif
// A simple array for POD types with the first SIZE elements stored in
// the object itself. It supports a subset of std::vector's operations.
template <typename T, std::size_t SIZE, typename Allocator = std::allocator<T> >
class Array : private Allocator {
// A buffer for POD types. It supports a subset of std::vector's operations.
template <typename T>
class Buffer {
private:
FMT_DISALLOW_COPY_AND_ASSIGN(Buffer);
protected:
T *ptr_;
std::size_t size_;
std::size_t capacity_;
T *ptr_;
T data_[SIZE];
void grow(std::size_t size);
Buffer(T *ptr = 0, std::size_t capacity = 0)
: ptr_(ptr), size_(0), capacity_(capacity) {}
// Free memory allocated by the array.
void free() {
if (ptr_ != data_) this->deallocate(ptr_, capacity_);
}
FMT_DISALLOW_COPY_AND_ASSIGN(Array);
virtual void grow(std::size_t size) = 0;
public:
explicit Array(const Allocator &alloc = Allocator())
: Allocator(alloc), size_(0), capacity_(SIZE), ptr_(data_) {}
~Array() { free(); }
virtual ~Buffer() {}
#if FMT_USE_RVALUE_REFERENCES
private:
// Move data from other to this array.
void move(Array &other) {
Allocator &this_alloc = *this, &other_alloc = other;
this_alloc = std::move(other_alloc);
size_ = other.size_;
capacity_ = other.capacity_;
if (other.ptr_ == other.data_) {
ptr_ = data_;
std::copy(other.data_, other.data_ + size_, make_ptr(data_, capacity_));
} else {
ptr_ = other.ptr_;
// Set pointer to the inline array so that delete is not called
// when freeing.
other.ptr_ = other.data_;
}
}
public:
Array(Array &&other) {
move(other);
}
Array& operator=(Array &&other) {
assert(this != &other);
free();
move(other);
return *this;
}
#endif
// Returns the size of this array.
// Returns the size of this buffer.
std::size_t size() const { return size_; }
// Returns the capacity of this array.
// Returns the capacity of this buffer.
std::size_t capacity() const { return capacity_; }
// Returns a copy of the allocator associated with this array.
Allocator get_allocator() const { return *this; }
// Resizes the array. If T is a POD type new elements are not initialized.
// Resizes the buffer. If T is a POD type new elements are not initialized.
void resize(std::size_t new_size) {
if (new_size > capacity_)
grow(new_size);
@@ -323,32 +283,15 @@ class Array : private Allocator {
ptr_[size_++] = value;
}
// Appends data to the end of the array.
// Appends data to the end of the buffer.
void append(const T *begin, const T *end);
T &operator[](std::size_t index) { return ptr_[index]; }
const T &operator[](std::size_t index) const { return ptr_[index]; }
};
template <typename T, std::size_t SIZE, typename Allocator>
void Array<T, SIZE, Allocator>::grow(std::size_t size) {
std::size_t new_capacity = (std::max)(size, capacity_ + capacity_ / 2);
T *new_ptr = this->allocate(new_capacity);
// The following code doesn't throw, so the raw pointer above doesn't leak.
std::copy(ptr_, ptr_ + size_, make_ptr(new_ptr, new_capacity));
std::size_t old_capacity = capacity_;
T *old_ptr = ptr_;
capacity_ = new_capacity;
ptr_ = new_ptr;
// deallocate may throw (at least in principle), but it doesn't matter since
// the array already uses the new storage and will deallocate it in case
// of exception.
if (old_ptr != data_)
this->deallocate(old_ptr, old_capacity);
}
template <typename T, std::size_t SIZE, typename Allocator>
void Array<T, SIZE, Allocator>::append(const T *begin, const T *end) {
template <typename T>
void Buffer<T>::append(const T *begin, const T *end) {
std::ptrdiff_t num_elements = end - begin;
if (size_ + num_elements > capacity_)
grow(size_ + num_elements);
@@ -356,6 +299,81 @@ void Array<T, SIZE, Allocator>::append(const T *begin, const T *end) {
size_ += num_elements;
}
// A memory buffer for POD types with the first SIZE elements stored in
// the object itself.
template <typename T, std::size_t SIZE, typename Allocator = std::allocator<T> >
class MemoryBuffer : private Allocator, public Buffer<T> {
private:
T data_[SIZE];
void grow(std::size_t size);
// Free memory allocated by the buffer.
void free() {
if (this->ptr_ != data_) this->deallocate(this->ptr_, this->capacity_);
}
public:
explicit MemoryBuffer(const Allocator &alloc = Allocator())
: Allocator(alloc), Buffer<T>(data_, SIZE) {}
~MemoryBuffer() { free(); }
#if FMT_USE_RVALUE_REFERENCES
private:
// Move data from other to this buffer.
void move(MemoryBuffer &other) {
Allocator &this_alloc = *this, &other_alloc = other;
this_alloc = std::move(other_alloc);
this->size_ = other.size_;
this->capacity_ = other.capacity_;
if (other.ptr_ == other.data_) {
this->ptr_ = data_;
std::copy(other.data_,
other.data_ + this->size_, make_ptr(data_, this->capacity_));
} else {
this->ptr_ = other.ptr_;
// Set pointer to the inline array so that delete is not called
// when freeing.
other.ptr_ = other.data_;
}
}
public:
MemoryBuffer(MemoryBuffer &&other) {
move(other);
}
MemoryBuffer &operator=(MemoryBuffer &&other) {
assert(this != &other);
free();
move(other);
return *this;
}
#endif
// Returns a copy of the allocator associated with this buffer.
Allocator get_allocator() const { return *this; }
};
template <typename T, std::size_t SIZE, typename Allocator>
void MemoryBuffer<T, SIZE, Allocator>::grow(std::size_t size) {
std::size_t new_capacity =
(std::max)(size, this->capacity_ + this->capacity_ / 2);
T *new_ptr = this->allocate(new_capacity);
// The following code doesn't throw, so the raw pointer above doesn't leak.
std::copy(this->ptr_,
this->ptr_ + this->size_, make_ptr(new_ptr, new_capacity));
std::size_t old_capacity = this->capacity_;
T *old_ptr = this->ptr_;
this->capacity_ = new_capacity;
this->ptr_ = new_ptr;
// deallocate may throw (at least in principle), but it doesn't matter since
// the buffer already uses the new storage and will deallocate it in case
// of exception.
if (old_ptr != data_)
this->deallocate(old_ptr, old_capacity);
}
#ifndef _MSC_VER
// Portable version of signbit.
// When compiled in C++11 mode signbit is no longer a macro but a function
@@ -437,24 +455,17 @@ class CharTraits<wchar_t> : public BasicCharTraits<wchar_t> {
const wchar_t *format, unsigned width, int precision, T value);
};
// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise.
template <bool FitsIn32Bits>
struct TypeSelector { typedef uint32_t Type; };
template <>
struct TypeSelector<false> { typedef uint64_t Type; };
// Checks if a number is negative - used to avoid warnings.
template <bool IsSigned>
struct SignChecker {
template <typename T>
static bool is_negative(T) { return false; }
static bool is_negative(T value) { return value < 0; }
};
template <>
struct SignChecker<true> {
struct SignChecker<false> {
template <typename T>
static bool is_negative(T value) { return value < 0; }
static bool is_negative(T) { return false; }
};
// Returns true if value is negative, false otherwise.
@@ -464,6 +475,13 @@ inline bool is_negative(T value) {
return SignChecker<std::numeric_limits<T>::is_signed>::is_negative(value);
}
// Selects uint32_t if FitsIn32Bits is true, uint64_t otherwise.
template <bool FitsIn32Bits>
struct TypeSelector { typedef uint32_t Type; };
template <>
struct TypeSelector<false> { typedef uint64_t Type; };
template <typename T>
struct IntTraits {
// Smallest of uint32_t and uint64_t that is large enough to represent
@@ -556,7 +574,7 @@ inline void format_decimal(Char *buffer, UInt value, unsigned num_digits) {
// It is only provided for Windows since other systems support UTF-8 natively.
class UTF8ToUTF16 {
private:
Array<wchar_t, INLINE_BUFFER_SIZE> buffer_;
MemoryBuffer<wchar_t, INLINE_BUFFER_SIZE> buffer_;
public:
explicit UTF8ToUTF16(StringRef s);
@@ -570,7 +588,7 @@ class UTF8ToUTF16 {
// It is only provided for Windows since other systems support UTF-8 natively.
class UTF16ToUTF8 {
private:
Array<char, INLINE_BUFFER_SIZE> buffer_;
MemoryBuffer<char, INLINE_BUFFER_SIZE> buffer_;
public:
UTF16ToUTF8() {}
@@ -608,7 +626,7 @@ struct NonZero<0> {
};
// The value of a formatting argument. It is a POD type to allow storage in
// internal::Array.
// internal::MemoryBuffer.
struct Value {
template <typename Char>
struct StringValue {
@@ -1421,12 +1439,13 @@ class SystemError : public internal::RuntimeError {
accessed as a C string with ``out.c_str()``.
\endrst
*/
template <typename Char, typename Allocator>
template <typename Char>
class BasicWriter {
private:
// Output buffer.
typedef internal::Array<Char, internal::INLINE_BUFFER_SIZE, Allocator> Array;
mutable Array buffer_;
internal::Buffer<Char> &buffer_;
FMT_DISALLOW_COPY_AND_ASSIGN(BasicWriter);
typedef typename internal::CharTraits<Char>::CharPtr CharPtr;
@@ -1465,7 +1484,7 @@ class BasicWriter {
// Formats an integer.
template <typename T, typename Spec>
void write_int(T value, const Spec &spec);
void write_int(T value, Spec spec);
// Formats a floating-point number (double or long double).
template <typename T>
@@ -1493,24 +1512,7 @@ class BasicWriter {
/**
Constructs a ``BasicWriter`` object.
*/
BasicWriter(const Allocator &alloc = Allocator()) : buffer_(alloc) {}
#if FMT_USE_RVALUE_REFERENCES
/**
Constructs a ``BasicWriter`` object moving the content of the other
object to it.
*/
BasicWriter(BasicWriter &&other) : buffer_(std::move(other.buffer_)) {}
/**
Moves the content of the other ``BasicWriter`` object to this one.
*/
BasicWriter& operator=(BasicWriter &&other) {
assert(this != &other);
buffer_ = std::move(other.buffer_);
return *this;
}
#endif
explicit BasicWriter(internal::Buffer<Char> &b) : buffer_(b) {}
/**
Returns the total number of characters written.
@@ -1631,7 +1633,7 @@ class BasicWriter {
}
template <typename T, typename Spec, typename FillChar>
BasicWriter &operator<<(const IntFormatSpec<T, Spec, FillChar> &spec) {
BasicWriter &operator<<(IntFormatSpec<T, Spec, FillChar> spec) {
internal::CharTraits<Char>::convert(FillChar());
write_int(spec.value(), spec);
return *this;
@@ -1648,10 +1650,10 @@ class BasicWriter {
void clear() FMT_NOEXCEPT(true) { buffer_.clear(); }
};
template <typename Char, typename Allocator>
template <typename Char>
template <typename StrChar>
typename BasicWriter<Char, Allocator>::CharPtr
BasicWriter<Char, Allocator>::write_str(
typename BasicWriter<Char>::CharPtr
BasicWriter<Char>::write_str(
const StrChar *s, std::size_t size, const AlignSpec &spec) {
CharPtr out = CharPtr();
if (spec.width() > size) {
@@ -1672,9 +1674,9 @@ typename BasicWriter<Char, Allocator>::CharPtr
return out;
}
template <typename Char, typename Allocator>
typename BasicWriter<Char, Allocator>::CharPtr
BasicWriter<Char, Allocator>::fill_padding(
template <typename Char>
typename BasicWriter<Char>::CharPtr
BasicWriter<Char>::fill_padding(
CharPtr buffer, unsigned total_size,
std::size_t content_size, wchar_t fill) {
std::size_t padding = total_size - content_size;
@@ -1687,10 +1689,10 @@ typename BasicWriter<Char, Allocator>::CharPtr
return content;
}
template <typename Char, typename Allocator>
template <typename Char>
template <typename Spec>
typename BasicWriter<Char, Allocator>::CharPtr
BasicWriter<Char, Allocator>::prepare_int_buffer(
typename BasicWriter<Char>::CharPtr
BasicWriter<Char>::prepare_int_buffer(
unsigned num_digits, const Spec &spec,
const char *prefix, unsigned prefix_size) {
unsigned width = spec.width();
@@ -1750,9 +1752,9 @@ typename BasicWriter<Char, Allocator>::CharPtr
return p - 1;
}
template <typename Char, typename Allocator>
template <typename Char>
template <typename T, typename Spec>
void BasicWriter<Char, Allocator>::write_int(T value, const Spec &spec) {
void BasicWriter<Char>::write_int(T value, Spec spec) {
unsigned prefix_size = 0;
typedef typename internal::IntTraits<T>::MainType UnsignedType;
UnsignedType abs_value = value;
@@ -1832,9 +1834,9 @@ void BasicWriter<Char, Allocator>::write_int(T value, const Spec &spec) {
}
}
template <typename Char, typename Allocator>
template <typename Char>
template <typename T>
void BasicWriter<Char, Allocator>::write_double(
void BasicWriter<Char>::write_double(
T value, const FormatSpec &spec) {
// Check type.
char type = spec.type();
@@ -1983,6 +1985,37 @@ void BasicWriter<Char, Allocator>::write_double(
}
}
template <typename Char, typename Allocator = std::allocator<Char> >
class BasicMemoryWriter : public BasicWriter<Char> {
private:
internal::MemoryBuffer<Char, internal::INLINE_BUFFER_SIZE, Allocator> buffer_;
public:
explicit BasicMemoryWriter(const Allocator& alloc = Allocator())
: BasicWriter<Char>(buffer_), buffer_(alloc) {}
#if FMT_USE_RVALUE_REFERENCES
/**
Constructs a ``BasicMemoryWriter`` object moving the content of the other
object to it.
*/
BasicMemoryWriter(BasicMemoryWriter &&other)
: BasicWriter<Char>(buffer_), buffer_(std::move(other.buffer_)) {
}
/**
Moves the content of the other ``BasicMemoryWriter`` object to this one.
*/
BasicMemoryWriter &operator=(BasicMemoryWriter &&other) {
buffer_ = std::move(other.buffer_);
return *this;
}
#endif
};
typedef BasicMemoryWriter<char> MemoryWriter;
typedef BasicMemoryWriter<wchar_t> WMemoryWriter;
// Formats a value.
template <typename Char, typename T>
void format(BasicFormatter<Char> &f, const Char *&format_str, const T &value) {
@@ -2051,13 +2084,13 @@ void print_colored(Color c, StringRef format, ArgList args);
\endrst
*/
inline std::string format(StringRef format_str, ArgList args) {
Writer w;
MemoryWriter w;
w.write(format_str, args);
return w.str();
}
inline std::wstring format(WStringRef format_str, ArgList args) {
WWriter w;
WMemoryWriter w;
w.write(format_str, args);
return w.str();
}
@@ -2110,7 +2143,7 @@ void printf(BasicWriter<Char> &w, BasicStringRef<Char> format, ArgList args) {
\endrst
*/
inline std::string sprintf(StringRef format, ArgList args) {
Writer w;
MemoryWriter w;
printf(w, format, args);
return w.str();
}