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 |