Chromium Code Reviews| Index: webrtc/base/buffer.h |
| diff --git a/webrtc/base/buffer.h b/webrtc/base/buffer.h |
| index 5c9380afee03a5e0825d92475e9ed578244314ae..b1b84f3cf765ee670e4deb14aee849b2479af63f 100644 |
| --- a/webrtc/base/buffer.h |
| +++ b/webrtc/base/buffer.h |
| @@ -18,6 +18,7 @@ |
| #include <utility> // std::swap (C++11 and later) |
| #include "webrtc/base/constructormagic.h" |
| +#include "webrtc/base/atomicops.h" |
| #include "webrtc/base/deprecation.h" |
| namespace rtc { |
| @@ -45,8 +46,136 @@ struct ByteType { |
| } // namespace internal |
| +// Data stored in a buffer. RefCounted and will be shared between buffers that |
| +// are cloned to avoid unnecessary allocations / copies. |
| +class BufferData { |
|
tommi
2016/02/14 09:03:42
should this be in the 'internal' namespace? I'm as
joachim
2016/02/14 11:10:19
You are right, changed.
|
| + public: |
| + BufferData(); |
| + |
| + explicit BufferData(const BufferData& data); |
| + |
| + // Construct data with the specified number of uninitialized bytes. |
| + explicit BufferData(size_t size); |
| + BufferData(size_t size, size_t capacity); |
| + |
| + // Construct data and copy the specified number of bytes into it. |
| + BufferData(const void* data, size_t size); |
| + BufferData(const void* data, size_t size, size_t capacity); |
| + |
| + size_t size() const { |
| + assert(IsConsistent()); |
|
tommi
2016/02/14 09:03:42
use RTC_DCHECK instead of assert()
joachim
2016/02/14 11:10:18
Changed here and in other places.
|
| + return size_; |
| + } |
| + size_t capacity() const { |
| + assert(IsConsistent()); |
| + return capacity_; |
| + } |
| + uint8_t* data() const { |
| + assert(IsConsistent()); |
| + return data_.get(); |
| + } |
| + |
| + void SetData(const void* data, size_t size) { |
| + assert(IsConsistent()); |
| + size_ = 0; |
| + AppendData(data, size); |
| + } |
| + |
| + void AppendData(const void* data, size_t size) { |
| + assert(IsConsistent()); |
| + const size_t new_size = size_ + size; |
| + EnsureCapacity(new_size); |
| + std::memcpy(data_.get() + size_, data, size); |
| + size_ = new_size; |
| + assert(IsConsistent()); |
| + } |
| + |
| + // Sets the size of the data. If the new size is smaller than the old, the |
| + // data contents will be kept but truncated; if the new size is greater, |
| + // the existing contents will be kept and the new space will be |
| + // uninitialized. |
| + void SetSize(size_t size) { |
| + EnsureCapacity(size); |
| + size_ = size; |
| + } |
| + |
| + // Ensure that the data size can be increased to at least capacity without |
| + // further reallocation. (Of course, this operation might need to reallocate |
| + // the buffer.) |
| + void EnsureCapacity(size_t capacity) { |
| + assert(IsConsistent()); |
| + if (capacity <= capacity_) { |
| + return; |
| + } |
| + |
| + std::unique_ptr<uint8_t[]> new_data(new uint8_t[capacity]); |
| + std::memcpy(new_data.get(), data_.get(), size_); |
| + data_ = std::move(new_data); |
| + capacity_ = capacity; |
| + assert(IsConsistent()); |
| + } |
| + |
| + // Resets the data to zero size and capacity. |
| + void Clear() { |
| + data_.reset(); |
| + size_ = 0; |
| + capacity_ = 0; |
| + assert(IsConsistent()); |
| + } |
| + |
| + // Reimplement parts of RefCountedObject which is not in rtc_base_approved. |
|
joachim
2016/02/13 16:08:46
Are there plans to move "refcount.h" into "rtc_bas
tommi
2016/02/14 09:03:42
Yes, that should be trivial.
Btw, does the refcoun
joachim
2016/02/14 11:10:18
Thanks for your CL to move it, please see my last
|
| + |
| + int AddRef() const { |
| + return AtomicOps::Increment(&ref_count_); |
| + } |
| + |
| + int Release() const { |
| + int count = AtomicOps::Decrement(&ref_count_); |
| + if (!count) { |
| + delete this; |
| + } |
| + return count; |
| + } |
| + |
| + // Returns true if the data is only used for a single Buffer. |
| + bool HasOneRef() const { |
| + return AtomicOps::AcquireLoad(&ref_count_) == 1; |
| + } |
| + |
| + private: |
| + friend class Buffer; |
| + |
| + ~BufferData(); |
| + |
| + // Precondition for all methods except Clear and the destructor. |
| + // Postcondition for all methods except move construction and move |
| + // assignment, which leave the moved-from object in a possibly inconsistent |
| + // state. |
| + bool IsConsistent() const { |
| + return (data_ || capacity_ == 0) && capacity_ >= size_; |
| + } |
| + |
| +#ifndef NDEBUG |
| + // Called when *this has been moved from. Conceptually it's a no-op, but we |
| + // can mutate the state slightly to help subsequent sanity checks catch bugs. |
| + void OnMovedFrom() { |
| + // Ensure that *this is always inconsistent, to provoke bugs. |
| + size_ = 1; |
| + capacity_ = 0; |
| + } |
| +#endif |
| + |
| + mutable volatile int ref_count_; |
| + |
| + size_t size_; |
| + size_t capacity_; |
| + std::unique_ptr<uint8_t[]> data_; |
| +}; |
| + |
| // Basic buffer class, can be grown and shrunk dynamically. |
| // Unlike std::string/vector, does not initialize data when expanding capacity. |
| +// The underlying memory is shared between copies of buffers and cloned if a |
| +// shared buffer is being modified. |
| class Buffer { |
| public: |
| Buffer(); // An empty buffer. |
| @@ -65,12 +194,12 @@ class Buffer { |
| template <typename T, typename internal::ByteType<T>::t = 0> |
| Buffer(const T* data, size_t size, size_t capacity) |
| : Buffer(size, capacity) { |
| - std::memcpy(data_.get(), data, size); |
| + std::memcpy(data_->data(), data, size); |
| } |
| // Construct a buffer from the contents of an array. |
| template <typename T, size_t N, typename internal::ByteType<T>::t = 0> |
| - Buffer(const T(&array)[N]) |
| + Buffer(const T(&array)[N]) // NOLINT: runtime/explicit |
| : Buffer(array, N) {} |
| ~Buffer(); |
| @@ -80,41 +209,45 @@ class Buffer { |
| template <typename T = uint8_t, typename internal::ByteType<T>::t = 0> |
| const T* data() const { |
| assert(IsConsistent()); |
| - return reinterpret_cast<T*>(data_.get()); |
| + return reinterpret_cast<T*>(data_->data()); |
| } |
| + // Get writable pointer to the data. This will create a copy of the underlying |
| + // data if it is shared with other buffers. |
| template <typename T = uint8_t, typename internal::ByteType<T>::t = 0> |
| T* data() { |
| - assert(IsConsistent()); |
| - return reinterpret_cast<T*>(data_.get()); |
| + CloneDataIfReferenced(); |
| + return reinterpret_cast<T*>(data_->data()); |
| } |
| size_t size() const { |
| assert(IsConsistent()); |
| - return size_; |
| + return data_->size(); |
| } |
| size_t capacity() const { |
| assert(IsConsistent()); |
| - return capacity_; |
| + return data_->capacity(); |
| } |
| Buffer& operator=(const Buffer& buf) { |
| - if (&buf != this) |
| - SetData(buf.data(), buf.size()); |
| + if (&buf != this) { |
| + SetData(buf); |
| + } |
| return *this; |
| } |
| Buffer& operator=(Buffer&& buf) { |
| - assert(IsConsistent()); |
| - assert(buf.IsConsistent()); |
| - size_ = buf.size_; |
| - capacity_ = buf.capacity_; |
| - data_ = std::move(buf.data_); |
| - buf.OnMovedFrom(); |
| + if (&buf != this) { |
| + assert(IsConsistent()); |
| + assert(buf.IsConsistent()); |
| + data_->Release(); |
| + data_ = std::move(buf.data_); |
| + buf.OnMovedFrom(); |
| + } |
| return *this; |
| } |
| bool operator==(const Buffer& buf) const { |
| - assert(IsConsistent()); |
| - return size_ == buf.size() && memcmp(data_.get(), buf.data(), size_) == 0; |
| + return data_ == buf.data_ || |
| + (size() == buf.size() && memcmp(data(), buf.data(), size()) == 0); |
| } |
| bool operator!=(const Buffer& buf) const { return !(*this == buf); } |
| @@ -124,24 +257,32 @@ class Buffer { |
| template <typename T, typename internal::ByteType<T>::t = 0> |
| void SetData(const T* data, size_t size) { |
| assert(IsConsistent()); |
| - size_ = 0; |
| - AppendData(data, size); |
| + if (!data_->HasOneRef()) { |
| + data_->Release(); |
| + data_ = new BufferData(data, size); |
| + } else { |
| + data_->SetData(data, size); |
| + } |
| } |
| template <typename T, size_t N, typename internal::ByteType<T>::t = 0> |
| void SetData(const T(&array)[N]) { |
| SetData(array, N); |
| } |
| - void SetData(const Buffer& buf) { SetData(buf.data(), buf.size()); } |
| + void SetData(const Buffer& buf) { |
| + if (&buf != this) { |
| + assert(IsConsistent()); |
| + assert(buf.IsConsistent()); |
| + buf.data_->AddRef(); |
| + data_->Release(); |
| + data_ = buf.data_; |
| + } |
| + } |
| // Append data to the buffer. Accepts the same types as the constructors. |
| template <typename T, typename internal::ByteType<T>::t = 0> |
| void AppendData(const T* data, size_t size) { |
| - assert(IsConsistent()); |
| - const size_t new_size = size_ + size; |
| - EnsureCapacity(new_size); |
| - std::memcpy(data_.get() + size_, data, size); |
| - size_ = new_size; |
| - assert(IsConsistent()); |
| + CloneDataIfReferenced(); |
| + data_->AppendData(data, size); |
| } |
| template <typename T, size_t N, typename internal::ByteType<T>::t = 0> |
| void AppendData(const T(&array)[N]) { |
| @@ -154,8 +295,8 @@ class Buffer { |
| // the existing contents will be kept and the new space will be |
| // uninitialized. |
| void SetSize(size_t size) { |
| - EnsureCapacity(size); |
| - size_ = size; |
| + CloneDataIfReferenced(); |
| + data_->SetSize(size); |
| } |
| // Ensure that the buffer size can be increased to at least capacity without |
| @@ -163,13 +304,11 @@ class Buffer { |
| // the buffer.) |
| void EnsureCapacity(size_t capacity) { |
| assert(IsConsistent()); |
| - if (capacity <= capacity_) |
| + if (capacity <= data_->capacity()) |
| return; |
| - std::unique_ptr<uint8_t[]> new_data(new uint8_t[capacity]); |
| - std::memcpy(new_data.get(), data_.get(), size_); |
| - data_ = std::move(new_data); |
| - capacity_ = capacity; |
| - assert(IsConsistent()); |
| + |
| + CloneDataIfReferenced(); |
| + data_->EnsureCapacity(capacity); |
| } |
| // b.Pass() does the same thing as std::move(b). |
| @@ -180,21 +319,20 @@ class Buffer { |
| return std::move(*this); |
| } |
| - // Resets the buffer to zero size and capacity. Works even if the buffer has |
| - // been moved from. |
| + // Resets the buffer to zero size and capacity. |
| void Clear() { |
| - data_.reset(); |
| - size_ = 0; |
| - capacity_ = 0; |
| + if (!data_->HasOneRef()) { |
| + data_->Release(); |
| + data_ = new BufferData(); |
| + } else { |
| + data_->Clear(); |
| + } |
| assert(IsConsistent()); |
| } |
| - // Swaps two buffers. Also works for buffers that have been moved from. |
| + // Swaps two buffers. |
| friend void swap(Buffer& a, Buffer& b) { |
| - using std::swap; |
| - swap(a.size_, b.size_); |
| - swap(a.capacity_, b.capacity_); |
| - swap(a.data_, b.data_); |
| + std::swap(a.data_, b.data_); |
| } |
| private: |
| @@ -203,27 +341,33 @@ class Buffer { |
| // assignment, which leave the moved-from object in a possibly inconsistent |
| // state. |
| bool IsConsistent() const { |
| - return (data_ || capacity_ == 0) && capacity_ >= size_; |
| + return data_ != nullptr && data_->IsConsistent(); |
| } |
| // Called when *this has been moved from. Conceptually it's a no-op, but we |
| // can mutate the state slightly to help subsequent sanity checks catch bugs. |
| void OnMovedFrom() { |
| -#ifdef NDEBUG |
| - // Make *this consistent and empty. Shouldn't be necessary, but better safe |
| - // than sorry. |
| - size_ = 0; |
| - capacity_ = 0; |
| -#else |
| + data_ = new BufferData(); |
| +#ifndef NDEBUG |
| // Ensure that *this is always inconsistent, to provoke bugs. |
| - size_ = 1; |
| - capacity_ = 0; |
| + data_->OnMovedFrom(); |
| #endif |
| } |
| - size_t size_; |
| - size_t capacity_; |
| - std::unique_ptr<uint8_t[]> data_; |
| + // Create a copy of the underlying data if it is referenced from other Buffer |
| + // objects. |
| + void CloneDataIfReferenced() { |
| + assert(IsConsistent()); |
| + if (data_->HasOneRef()) { |
| + return; |
| + } |
| + |
| + BufferData* new_data = new BufferData(*data_); |
| + data_->Release(); |
| + data_ = new_data; |
| + } |
| + |
| + BufferData* data_; |
| }; |
| } // namespace rtc |