Commit 322a5469 authored by Brett Wilson's avatar Brett Wilson Committed by Commit Bot

Add base::circular_deque container.

This container behaves like a std::deque (minus random access insert/erase) but uses
an array as the underlying storage. Additional details are documented in the header.

When DCHECK is enabled, the iterators do extra checks to ensure they are not being
used after container mutations.

A new helper class called base::internal::ArrayBuffer is added to be the low-level storage.
This split was inspired by WTF::Deque although the functionality is quite different: The
added one is mostly a holder for template move and destruct helpers, while the WTF one is
to facilitate copy-on-write and buffer sharing (which is not supported by this one).

The base::void_t is moved from base::internal namespace. This C++17 emulation need not
be strictly internal to base and once I understood how to use it, it seems quite useful.

Added base::internal::is_iterator to detect if an item is an iterator. This is required
by one of the STL deque functions but the exact implementation is not specified by the
standard. This implementation checks for the presence of iterator_category in the
iterator traits.

Contains minor fixes to move_only_int.h testing helper and the addition to a non-movable
copy_only_int.h

Related docs by dskiba:
https://docs.google.com/document/d/1PEuPnSW54LaoWpUIEAHobqGt9nbD2DgQ_W5DdlJOkJU
https://docs.google.com/document/d/1YL1FORFMWo0FK0lMg7WsImnjNQ3ZpY0nK0NHGjkeHT4

Future plans:
 1. Replace some std::deque uses around the code base as a smoketest.
 2. Add a presubmit to prevent additions of std::deque and std::queue.
 3. Replace the remaining uses.

Change-Id: Ic1a5c804da90514782a6eae4984d916da45c0d32
Reviewed-on: https://chromium-review.googlesource.com/582498
Commit-Queue: Brett Wilson <brettw@chromium.org>
Reviewed-by: default avatarVladimir Levin <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#491883}
parent 7ba924c0
...@@ -1085,3 +1085,8 @@ template("assert_valid_out_dir") { ...@@ -1085,3 +1085,8 @@ template("assert_valid_out_dir") {
assert_valid_out_dir("_unused") { assert_valid_out_dir("_unused") {
actual_sources = [ "$root_build_dir/foo" ] actual_sources = [ "$root_build_dir/foo" ]
} }
a = "foo"
b = "bar"
c = a + b
print(c)
...@@ -230,6 +230,7 @@ component("base") { ...@@ -230,6 +230,7 @@ component("base") {
"command_line.h", "command_line.h",
"compiler_specific.h", "compiler_specific.h",
"containers/adapters.h", "containers/adapters.h",
"containers/circular_deque.h",
"containers/flat_map.h", "containers/flat_map.h",
"containers/flat_set.h", "containers/flat_set.h",
"containers/flat_tree.h", "containers/flat_tree.h",
...@@ -238,6 +239,7 @@ component("base") { ...@@ -238,6 +239,7 @@ component("base") {
"containers/mru_cache.h", "containers/mru_cache.h",
"containers/small_map.h", "containers/small_map.h",
"containers/stack_container.h", "containers/stack_container.h",
"containers/vector_buffer.h",
"cpu.cc", "cpu.cc",
"cpu.h", "cpu.h",
"critical_closure.h", "critical_closure.h",
...@@ -1977,6 +1979,7 @@ test("base_unittests") { ...@@ -1977,6 +1979,7 @@ test("base_unittests") {
"cancelable_callback_unittest.cc", "cancelable_callback_unittest.cc",
"command_line_unittest.cc", "command_line_unittest.cc",
"containers/adapters_unittest.cc", "containers/adapters_unittest.cc",
"containers/circular_deque_unittest.cc",
"containers/flat_map_unittest.cc", "containers/flat_map_unittest.cc",
"containers/flat_set_unittest.cc", "containers/flat_set_unittest.cc",
"containers/flat_tree_unittest.cc", "containers/flat_tree_unittest.cc",
...@@ -1985,6 +1988,7 @@ test("base_unittests") { ...@@ -1985,6 +1988,7 @@ test("base_unittests") {
"containers/mru_cache_unittest.cc", "containers/mru_cache_unittest.cc",
"containers/small_map_unittest.cc", "containers/small_map_unittest.cc",
"containers/stack_container_unittest.cc", "containers/stack_container_unittest.cc",
"containers/vector_buffer_unittest.cc",
"cpu_unittest.cc", "cpu_unittest.cc",
"debug/activity_analyzer_unittest.cc", "debug/activity_analyzer_unittest.cc",
"debug/activity_tracker_unittest.cc", "debug/activity_tracker_unittest.cc",
......
...@@ -210,6 +210,51 @@ The initial size in the above table is assuming a very small inline table. The ...@@ -210,6 +210,51 @@ The initial size in the above table is assuming a very small inline table. The
actual size will be sizeof(int) + min(sizeof(std::map), sizeof(T) * actual size will be sizeof(int) + min(sizeof(std::map), sizeof(T) *
inline\_size). inline\_size).
# Deque
### Usage advice
Chromium code should always use `base::circular_deque` or `base::queue` in
preference to `std::deque` or `std::queue` due to memory usage and platform
variation.
The `base::deque` implementation (and the `base::queue` which uses it)
provide performance consistent across platforms that better matches most
programmer's expectations on performance (it doesn't waste as much space as
libc++ and doesn't do as many heap allocations as MSVC).
Since `base::deque` does not have stable iterators and it does not support
random-access insert and erase, it may not be appropriate for all uses. If
you need these, consider using a `std::list` which will provide constant
time insert and erase.
### std::deque and std::queue
The implementation of `std::deque` varies considerably which makes it hard to
reason about. All implementations use a sequence of data blocks referenced by
an array of pointers. The standard guarantees random access, amortized
constant operations at the ends, and linear mutations in the middle.
In Microsoft's implementation, each block is 16 bytes or the size of the
contained element. This means in practice that every expansion of the deque
of non-trivial classes requires a heap allocation. libc++ (on Android and Mac)
uses 4K blocks which elimiates the problem of many heap allocations, but
generally wastes a large amount of space (an Android analysis revealed more
than 2.5MB wasted space from deque alone, resulting in some optimizations).
libstdc++ uses an intermediate-size 512 byte buffer.
### base::circular_deque and base::queue
A deque implemented as a circular buffer in an array. The underlying array will
grow like a `std::vector` while the beginning and end of the deque will move
around. The items will wrap around the underlying buffer so the storage will
not be contiguous, but fast random access iterators are still possible.
When the underlying buffer is filled, it will be reallocated and the constents
moved (like a `std::vector`). As a result, iterators are not stable across
mutations. Like a vector, it will not shrink its underlying storage. Consider
calling `shrink_to_fit` if you delete many items that you don't plan to re-add.
## Appendix ## Appendix
### Code for map code size comparison ### Code for map code size comparison
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_CONTAINERS_CIRCULAR_DEQUE_H_
#define BASE_CONTAINERS_CIRCULAR_DEQUE_H_
#include <cstddef>
#include <iterator>
#include <type_traits>
#include <utility>
#include "base/containers/vector_buffer.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/template_util.h"
// base::circular_deque is similar to std::deque. Unlike std::deque, the
// storage is provided in a flat circular buffer conceptually similar to a
// vector. The beginning and end will wrap around as necessary so that
// pushes and pops will be constant time as long as a capacity expansion is
// not required.
//
// The API should be identical to std::deque with the following differences:
//
// - ITERATORS ARE NOT STABLE. Mutating the container will invalidate all
// iterators.
//
// - Insertions may resize the vector and so are not constant time (std::deque
// guarantees constant time for insertions at the ends).
//
// - Container-wide comparisons are not implemented. If you want to compare
// two containers, use an algorithm so the expensive iteration is explicit.
//
// - Insert and erase in the middle is not supported. This complicates the
// implementation and is not necessary for our current goals. We can
// consider adding this in the future if necessary. But consider that
// the implementation will be relatively inefficient (linear time). If
// a use wants arbitrary queue mutations, consider a std::list.
//
// If you want a similar container with only a queue API, use base::queue in
// base/containers/queue.h.
//
// Constructors:
// circular_deque();
// circular_deque(size_t count);
// circular_deque(size_t count, const T& value);
// circular_deque(InputIterator first, InputIterator last);
// circular_deque(const circular_deque&);
// circular_deque(circular_deque&&);
// circular_deque(std::initializer_list<value_type>);
//
// Assignment functions:
// circular_deque& operator=(const circular_deque&);
// circular_deque& operator=(circular_deque&&);
// circular_deque& operator=(std::initializer_list<T>);
// void assign(size_t count, const T& value);
// void assign(InputIterator first, InputIterator last);
// void assign(std::initializer_list<T> value);
//
// Random accessors:
// T& at(size_t);
// const T& at(size_t) const;
// T& operator[](size_t);
// const T& operator[](size_t) const;
//
// End accessors:
// T& front();
// const T& front() const;
// T& back();
// const T& back() const;
//
// Iterator functions:
// iterator begin();
// const_iterator begin() const;
// const_iterator cbegin() const;
// iterator end();
// const_iterator end() const;
// const_iterator cend() const;
// reverse_iterator rbegin();
// const_reverse_iterator rbegin() const;
// const_reverse_iterator crbegin() const;
// reverse_iterator rend();
// const_reverse_iterator rend() const;
// const_reverse_iterator crend() const;
//
// Memory management:
// void reserve(size_t);
// size_t capacity() const;
// void shrink_to_fit();
//
// Size management:
// void clear();
// bool empty() const;
// size_t size() const;
// void resize(size_t);
// void resize(size_t count, const T& value);
//
// End insert and erase:
// void push_front(const T&);
// void push_front(T&&);
// void push_back(const T&);
// void push_back(T&&);
// void emplace_front(Args&&...);
// void emplace_back(Args&&...);
// void pop_front();
// void pop_back();
//
// General:
// void swap(circular_deque&);
namespace base {
template <class T>
class circular_deque;
namespace internal {
template <typename T>
class circular_deque_const_iterator {
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = const T*;
using reference = const T&;
using iterator_category = std::random_access_iterator_tag;
circular_deque_const_iterator(const circular_deque<T>* parent, size_t index)
: parent_deque_(parent), index_(index) {
#if DCHECK_IS_ON()
created_generation_ = parent->generation_;
#endif // DCHECK_IS_ON()
}
// Dereferencing.
const T& operator*() const {
CheckUnstableUsage();
parent_deque_->CheckValidIndex(index_);
return parent_deque_->buffer_[index_];
}
const T* operator->() const {
CheckUnstableUsage();
parent_deque_->CheckValidIndex(index_);
return &parent_deque_->buffer_[index_];
}
// Increment and decrement.
circular_deque_const_iterator& operator++() {
Increment();
return *this;
}
circular_deque_const_iterator operator++(int) {
circular_deque_const_iterator ret = *this;
Increment();
return ret;
}
circular_deque_const_iterator& operator--() {
Decrement();
return *this;
}
circular_deque_const_iterator operator--(int) {
circular_deque_const_iterator ret = *this;
Decrement();
return ret;
}
// Random access mutation.
friend circular_deque_const_iterator operator+(
const circular_deque_const_iterator& iter,
difference_type offset) {
circular_deque_const_iterator ret = iter;
ret.Add(offset);
return ret;
}
circular_deque_const_iterator& operator+=(difference_type offset) {
Add(offset);
return *this;
}
friend circular_deque_const_iterator operator-(
const circular_deque_const_iterator& iter,
difference_type offset) {
circular_deque_const_iterator ret = iter;
ret.Add(-offset);
return ret;
}
circular_deque_const_iterator& operator-=(difference_type offset) {
Add(-offset);
return *this;
}
friend std::ptrdiff_t operator-(const circular_deque_const_iterator& lhs,
const circular_deque_const_iterator& rhs) {
lhs.CheckComparable(rhs);
return lhs.OffsetFromBegin() - rhs.OffsetFromBegin();
}
// Comparisons.
friend bool operator==(const circular_deque_const_iterator& lhs,
const circular_deque_const_iterator& rhs) {
lhs.CheckComparable(rhs);
return lhs.index_ == rhs.index_;
}
friend bool operator!=(const circular_deque_const_iterator& lhs,
const circular_deque_const_iterator& rhs) {
return !(lhs == rhs);
}
friend bool operator<(const circular_deque_const_iterator& lhs,
const circular_deque_const_iterator& rhs) {
lhs.CheckComparable(rhs);
return lhs.OffsetFromBegin() < rhs.OffsetFromBegin();
}
friend bool operator<=(const circular_deque_const_iterator& lhs,
const circular_deque_const_iterator& rhs) {
return !(lhs > rhs);
}
friend bool operator>(const circular_deque_const_iterator& lhs,
const circular_deque_const_iterator& rhs) {
lhs.CheckComparable(rhs);
return lhs.OffsetFromBegin() > rhs.OffsetFromBegin();
}
friend bool operator>=(const circular_deque_const_iterator& lhs,
const circular_deque_const_iterator& rhs) {
return !(lhs < rhs);
}
protected:
// Returns the offset from the beginning index of the buffer to the current
// iten.
size_t OffsetFromBegin() const {
if (index_ >= parent_deque_->begin_)
return index_ - parent_deque_->begin_; // On the same side as begin.
return parent_deque_->buffer_.capacity() - parent_deque_->begin_ + index_;
}
// Most uses will be ++ and -- so use a simplified implementation.
void Increment() {
CheckUnstableUsage();
parent_deque_->CheckValidIndex(index_);
index_++;
if (index_ == parent_deque_->buffer_.capacity())
index_ = 0;
}
void Decrement() {
CheckUnstableUsage();
parent_deque_->CheckValidIndexOrEnd(index_);
if (index_ == 0)
index_ = parent_deque_->buffer_.capacity() - 1;
else
index_--;
}
void Add(difference_type delta) {
CheckUnstableUsage();
parent_deque_->CheckValidIndex(index_);
difference_type new_offset = OffsetFromBegin() + delta;
DCHECK(new_offset >= 0 &&
new_offset <= static_cast<difference_type>(parent_deque_->size()));
index_ = (new_offset + parent_deque_->begin_) %
parent_deque_->buffer_.capacity();
}
#if DCHECK_IS_ON()
void CheckUnstableUsage() const {
// Since circular_deque doesn't guarantee stability, any attempt to
// dereference this iterator after a mutation (i.e. the generation doesn't
// match the original) in the container is illegal.
DCHECK(parent_deque_);
DCHECK_EQ(created_generation_, parent_deque_->generation_)
<< "circular_deque iterator dereferenced after mutation.";
}
void CheckComparable(const circular_deque_const_iterator& other) const {
DCHECK_EQ(parent_deque_, other.parent_deque_);
CheckUnstableUsage();
other.CheckUnstableUsage();
}
#else
inline void CheckUnstableUsage() const {}
inline void CheckComparable(const circular_deque_const_iterator&) const {}
#endif // DCHECK_IS_ON()
const circular_deque<T>* parent_deque_;
size_t index_;
#if DCHECK_IS_ON()
// The generation of the parent deque when this iterator was created. The
// container will update the generation for every modification so we can
// test if the container was modified by comparing them.
uint64_t created_generation_;
#endif // DCHECK_IS_ON()
};
template <typename T>
class circular_deque_iterator : public circular_deque_const_iterator<T> {
using base = circular_deque_const_iterator<T>;
public:
using difference_type = std::ptrdiff_t;
using value_type = T;
using pointer = T*;
using reference = T&;
using iterator_category = std::random_access_iterator_tag;
// Expose the base class' constructor.
using base::circular_deque_const_iterator;
// Dereferencing.
T& operator*() { return const_cast<T&>(base::operator*()); }
T* operator->() { return const_cast<T*>(base::operator->()); }
// Random access mutation.
friend circular_deque_iterator operator+(const circular_deque_iterator& iter,
difference_type offset) {
circular_deque_iterator ret = iter;
ret.Add(offset);
return ret;
}
circular_deque_iterator& operator+=(difference_type offset) {
base::Add(offset);
return *this;
}
friend circular_deque_iterator operator-(const circular_deque_iterator& iter,
difference_type offset) {
circular_deque_iterator ret = iter;
ret.Add(-offset);
return ret;
}
circular_deque_iterator& operator-=(difference_type offset) {
base::Add(-offset);
return *this;
}
// Increment and decrement.
circular_deque_iterator& operator++() {
base::Increment();
return *this;
}
circular_deque_iterator operator++(int) {
circular_deque_iterator ret = *this;
base::Increment();
return ret;
}
circular_deque_iterator& operator--() {
base::Decrement();
return *this;
}
circular_deque_iterator operator--(int) {
circular_deque_iterator ret = *this;
base::Decrement();
return ret;
}
};
} // namespace internal
template <typename T>
class circular_deque {
private:
using VectorBuffer = internal::VectorBuffer<T>;
public:
using value_type = T;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using iterator = internal::circular_deque_iterator<T>;
using const_iterator = internal::circular_deque_const_iterator<T>;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
// ---------------------------------------------------------------------------
// Constructor
circular_deque() = default;
// Constructs with |count| copies of |value| or default constructed version.
circular_deque(size_type count) { resize(count); }
circular_deque(size_type count, const T& value) { resize(count, value); }
// Range constructor.
template <class InputIterator>
circular_deque(InputIterator first, InputIterator last) {
assign(first, last);
}
// Copy/move.
circular_deque(const circular_deque& other) : buffer_(other.size() + 1) {
assign(other.begin(), other.end());
}
circular_deque(circular_deque&& other) noexcept
: buffer_(std::move(other.buffer_)),
begin_(other.begin_),
end_(other.end_) {
other.begin_ = 0;
other.end_ = 0;
}
circular_deque(std::initializer_list<value_type> init) { assign(init); }
~circular_deque() { DestructRange(begin_, end_); }
// ---------------------------------------------------------------------------
// Assignments.
//
// All of these may invalidate iterators and references.
circular_deque& operator=(const circular_deque& other) {
reserve(other.size());
assign(other.begin(), other.end());
return *this;
}
circular_deque& operator=(circular_deque&& other) noexcept {
// We're about to overwrite the buffer, so don't free it in clear to
// avoid doing it twice.
ClearRetainCapacity();
buffer_ = std::move(other.buffer_);
begin_ = other.begin_;
end_ = other.end_;
other.begin_ = 0;
other.end_ = 0;
IncrementGeneration();
return *this;
}
circular_deque& operator=(std::initializer_list<value_type> ilist) {
reserve(ilist.size());
assign(std::begin(ilist), std::end(ilist));
return *this;
}
void assign(size_type count, const value_type& value) {
ClearRetainCapacity();
reserve(count);
for (size_t i = 0; i < count; i++)
emplace_back(value);
IncrementGeneration();
}
// This variant should be enabled only when InputIterator is an iterator.
template <typename InputIterator>
typename std::enable_if<::base::internal::is_iterator<InputIterator>::value,
void>::type
assign(InputIterator first, InputIterator last) {
// Possible future enhancement, dispatch on iterator tag type. For forward
// iterators we can use std::difference to preallocate the space required
// and only do one copy.
ClearRetainCapacity();
for (; first != last; ++first)
emplace_back(*first);
IncrementGeneration();
}
void assign(std::initializer_list<value_type> value) {
reserve(std::distance(value.begin(), value.end()));
assign(value.begin(), value.end());
}
// ---------------------------------------------------------------------------
// Accessors.
//
// Since this class assumes no exceptions, at() and operator[] are equivalent.
const value_type& at(size_type i) const {
DCHECK(i < size());
size_t right_size = buffer_.capacity() - begin_;
if (begin_ <= end_ || i < right_size)
return buffer_[begin_ + i];
return buffer_[i - right_size];
}
value_type& at(size_type i) {
return const_cast<value_type&>(
const_cast<const circular_deque*>(this)->at(i));
}
value_type& operator[](size_type i) { return at(i); }
const value_type& operator[](size_type i) const {
return const_cast<circular_deque*>(this)->at(i);
}
value_type& front() {
DCHECK(!empty());
return buffer_[begin_];
}
const value_type& front() const {
DCHECK(!empty());
return buffer_[begin_];
}
value_type& back() {
DCHECK(!empty());
return *(--end());
}
const value_type& back() const {
DCHECK(!empty());
return *(--end());
}
// ---------------------------------------------------------------------------
// Iterators.
iterator begin() { return iterator(this, begin_); }
const_iterator begin() const { return const_iterator(this, begin_); }
const_iterator cbegin() const { return const_iterator(this, begin_); }
iterator end() { return iterator(this, end_); }
const_iterator end() const { return const_iterator(this, end_); }
const_iterator cend() const { return const_iterator(this, end_); }
reverse_iterator rbegin() { return reverse_iterator(begin()); }
const_reverse_iterator rbegin() const {
return const_reverse_iterator(begin());
}
const_reverse_iterator crbegin() const { return rbegin(); }
reverse_iterator rend() { return reverse_iterator(end()); }
const_reverse_iterator rend() const { return const_reverse_iterator(end()); }
const_reverse_iterator crend() const { return rend(); }
// ---------------------------------------------------------------------------
// Memory management.
void reserve(size_type new_capacity) {
if (new_capacity > capacity())
ExpandCapacityTo(new_capacity + 1);
}
size_type capacity() const {
// One item is wasted to indicate end().
return buffer_.capacity() == 0 ? 0 : buffer_.capacity() - 1;
}
void shrink_to_fit() {
if (empty()) {
// Optimize empty case to really delete everything if there was
// something.
if (buffer_.capacity())
buffer_ = VectorBuffer();
} else {
// One item is wasted to indicate end().
ExpandCapacityTo(size() + 1);
}
}
// ---------------------------------------------------------------------------
// Size management.
// This will additionally reset the capacity() to 0.
void clear() {
// This can't resize(0) because that requires a default constructor to
// compile, which not all contained classes may implement.
ClearRetainCapacity();
buffer_ = VectorBuffer();
}
bool empty() const { return begin_ == end_; }
size_type size() const {
if (begin_ <= end_)
return end_ - begin_;
return buffer_.capacity() - begin_ + end_;
}
// When reducing size, the elements are deleted from the end. When expanding
// size, elements are added to the end with |value| or the default
// constructed version.
//
// There are two versions rather than using a default value to avoid
// creating a temporary when shrinking (when it's not needed). Plus if
// the default constructor is desired when expanding usually just calling it
// for each element is faster than making a default-constructed temporary and
// copying it.
void resize(size_type count) {
// SEE BELOW VERSION if you change this. The code is mostly the same.
if (count > size()) {
// This could be slighly more efficient but expanding a queue with
// identical elements is unusual and the extra computations of emplacing
// one-by-one will typically be small relative to calling the constructor
// for every item.
ExpandCapacityIfNecessary(count - size());
while (size() < count)
emplace_back();
} else if (count < size()) {
// This doesn't resize the storage.
// TODO(brettw) revisit this decision (change below version also).
size_t new_end = (begin_ + count) % buffer_.capacity();
DestructRange(new_end, end_);
end_ = new_end;
}
IncrementGeneration();
}
void resize(size_type count, const value_type& value) {
// SEE ABOVE VERSION if you change this. The code is mostly the same.
if (count > size()) {
ExpandCapacityIfNecessary(count - size());
while (size() < count)
emplace_back(value);
} else if (count < size()) {
size_t new_end = (begin_ + count) % buffer_.capacity();
DestructRange(new_end, end_);
end_ = new_end;
}
IncrementGeneration();
}
// ---------------------------------------------------------------------------
// Insert and erase.
//
// These bulk insert operations are not provided as described in the file
// level comment above:
//
// void insert(const_iterator pos, size_type count, const T& value);
// void insert(const_iterator pos, InputIterator first, InputIterator last);
// iterator insert(const_iterator pos, const T& value);
// iterator insert(const_iterator pos, T&& value);
// iterator emplace(const_iterator pos, Args&&... args);
//
// iterator erase(const_iterator pos);
// iterator erase(const_iterator first, const_iterator last);
// ---------------------------------------------------------------------------
// Begin/end operations.
void push_front(const T& value) { emplace_front(value); }
void push_front(T&& value) { emplace_front(std::move(value)); }
void push_back(const T& value) { emplace_back(value); }
void push_back(T&& value) { emplace_back(std::move(value)); }
template <class... Args>
void emplace_front(Args&&... args) {
ExpandCapacityIfNecessary(1);
if (begin_ == 0)
begin_ = buffer_.capacity() - 1;
else
begin_--;
IncrementGeneration();
new (&buffer_[begin_]) T(std::forward<Args>(args)...);
}
template <class... Args>
void emplace_back(Args&&... args) {
ExpandCapacityIfNecessary(1);
new (&buffer_[end_]) T(std::forward<Args>(args)...);
if (end_ == buffer_.capacity() - 1)
end_ = 0;
else
end_++;
IncrementGeneration();
}
void pop_front() {
DCHECK(size());
buffer_.DestructRange(&buffer_[begin_], &buffer_[begin_ + 1]);
begin_++;
if (begin_ == buffer_.capacity())
begin_ = 0;
// Technically popping will not invalidate any iterators since the
// underlying buffer will be stable. But in the future we may want to add a
// feature that resizes the buffer smaller if there is too much wasted
// space. This ensures we can make such a change safely.
IncrementGeneration();
}
void pop_back() {
DCHECK(size());
if (end_ == 0)
end_ = buffer_.capacity() - 1;
else
end_--;
buffer_.DestructRange(&buffer_[end_], &buffer_[end_ + 1]);
// See pop_front comment about why this is here.
IncrementGeneration();
}
// ---------------------------------------------------------------------------
// General operations.
void swap(circular_deque& other) {
std::swap(buffer_, other.buffer_);
std::swap(begin_, other.begin_);
std::swap(end_, other.end_);
IncrementGeneration();
}
friend void swap(circular_deque& lhs, circular_deque& rhs) { lhs.swap(rhs); }
private:
friend internal::circular_deque_iterator<T>;
friend internal::circular_deque_const_iterator<T>;
// Moves the items in the given circular buffer to the current one. The
// source is moved from so will become invalid. The destination buffer must
// have already been allocated with enough size.
static void MoveBuffer(VectorBuffer& from_buf,
size_t from_begin,
size_t from_end,
VectorBuffer* to_buf,
size_t* to_begin,
size_t* to_end) {
size_t from_capacity = from_buf.capacity();
*to_begin = 0;
if (from_begin < from_end) {
// Contiguous.
from_buf.MoveRange(&from_buf[from_begin], &from_buf[from_end],
to_buf->begin());
*to_end = from_end - from_begin;
} else if (from_begin > from_end) {
// Discontiguous, copy the right side to the beginning of the new buffer.
from_buf.MoveRange(&from_buf[from_begin], &from_buf[from_capacity],
to_buf->begin());
size_t right_size = from_capacity - from_begin;
// Append the left side.
from_buf.MoveRange(&from_buf[0], &from_buf[from_end],
&(*to_buf)[right_size]);
*to_end = right_size + from_end;
} else {
// No items.
*to_end = 0;
}
}
// Expands the buffer size. This assumes the size is larger than the
// number of elements in the vector (it won't call delete on anything).
// Note the capacity passed here will be one larger than the "publicly
// exposed capacity" to account for the unused end element.
void ExpandCapacityTo(size_t new_capacity) {
VectorBuffer new_buffer(new_capacity);
MoveBuffer(buffer_, begin_, end_, &new_buffer, &begin_, &end_);
buffer_ = std::move(new_buffer);
}
void ExpandCapacityIfNecessary(size_t additional_elts) {
// Capacity is internal capacity, which is one extra.
size_t min_new_capacity = size() + additional_elts + 1;
if (buffer_.capacity() >= min_new_capacity)
return; // Already enough room.
// Start allocating nonempty buffers with this many entries.
constexpr size_t min_slots = 4;
min_new_capacity = std::max(min_new_capacity, min_slots);
// std::vector always grows by at least 50%. WTF::Deque grows by at least
// 25%. We expect queue workloads to generally stay at a similar size and
// grow less than a vector might, so use 25%.
size_t new_capacity = std::max(
min_new_capacity, buffer_.capacity() + buffer_.capacity() / 4 + 1);
ExpandCapacityTo(new_capacity);
}
// Backend for clear() but does not resize the internal buffer.
void ClearRetainCapacity() {
// This can't resize(0) because that requires a default constructor to
// compile, which not all contained classes may implement.
DestructRange(begin_, end_);
begin_ = 0;
end_ = 0;
IncrementGeneration();
}
// Calls destructors for the given begin->end indices. The indices may wrap
// around. The buffer is not resized, and the begin_ and end_ members are
// not changed.
void DestructRange(size_t begin, size_t end) {
if (end == begin) {
return;
} else if (end > begin) {
buffer_.DestructRange(&buffer_[begin], &buffer_[end]);
} else {
buffer_.DestructRange(&buffer_[begin], &buffer_[buffer_.capacity()]);
buffer_.DestructRange(&buffer_[0], &buffer_[end]);
}
}
#if DCHECK_IS_ON()
// Asserts the given index is dereferencable. The index is an index into the
// buffer, not an index used by operator[] or at() which will be offsets from
// begin.
void CheckValidIndex(size_t i) const {
if (begin_ <= end_)
DCHECK(i >= begin_ && i < end_);
else
DCHECK((i >= begin_ && i < buffer_.capacity()) || i < end_);
}
// Asserts the given index is either dereferencable or points to end().
void CheckValidIndexOrEnd(size_t i) const {
if (i != end_)
CheckValidIndex(i);
}
// See generation_ below.
void IncrementGeneration() { generation_++; }
#else
// No-op versions of these functions for release builds.
void CheckValidIndex(size_t) const {}
void CheckValidIndexOrEnd(size_t) const {}
void IncrementGeneration() {}
#endif
// Danger, the buffer_.capacity() is capacity() + 1 since there is an
// extra item to indicate the end. Container internal code will want to use
// buffer_.capacity() for offset computations.
VectorBuffer buffer_;
size_type begin_ = 0;
size_type end_ = 0;
#if DCHECK_IS_ON()
// Incremented every time a modification that could affect iterator
// invalidations.
uint64_t generation_ = 0;
#endif
};
} // namespace base
#endif // BASE_CONTAINERS_CIRCULAR_DEQUE_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/containers/circular_deque.h"
#include "base/test/copy_only_int.h"
#include "base/test/move_only_int.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::internal::VectorBuffer;
namespace base {
namespace {
circular_deque<int> MakeSequence(size_t max) {
circular_deque<int> ret;
for (size_t i = 0; i < max; i++)
ret.push_back(i);
return ret;
}
// Cycles through the queue, popping items from the back and pushing items
// at the front to validate behavior across different configurations of the
// queue in relation to the underlying buffer. The tester closure is run for
// each cycle.
template <class QueueT, class Tester>
void CycleTest(circular_deque<QueueT>& queue, const Tester& tester) {
size_t steps = queue.size() * 2;
for (size_t i = 0; i < steps; i++) {
tester(queue, i);
queue.pop_back();
queue.push_front(QueueT());
}
}
class DestructorCounter {
public:
DestructorCounter(int* counter) : counter_(counter) {}
~DestructorCounter() { ++(*counter_); }
private:
int* counter_;
};
} // namespace
TEST(CircularDeque, FillConstructor) {
constexpr size_t num_elts = 9;
std::vector<int> foo(15);
EXPECT_EQ(15u, foo.size());
// Fill with default constructor.
{
circular_deque<int> buf(num_elts);
EXPECT_EQ(num_elts, buf.size());
EXPECT_EQ(num_elts, static_cast<size_t>(buf.end() - buf.begin()));
for (size_t i = 0; i < num_elts; i++)
EXPECT_EQ(0, buf[i]);
}
// Fill with explicit value.
{
int value = 199;
circular_deque<int> buf(num_elts, value);
EXPECT_EQ(num_elts, buf.size());
EXPECT_EQ(num_elts, static_cast<size_t>(buf.end() - buf.begin()));
for (size_t i = 0; i < num_elts; i++)
EXPECT_EQ(value, buf[i]);
}
}
TEST(CircularDeque, CopyAndRangeConstructor) {
int values[] = {1, 2, 3, 4, 5, 6};
circular_deque<CopyOnlyInt> first(std::begin(values), std::end(values));
circular_deque<CopyOnlyInt> second(first);
EXPECT_EQ(6u, second.size());
for (int i = 0; i < 6; i++)
EXPECT_EQ(i + 1, second[i].data());
}
TEST(CircularDeque, MoveConstructor) {
int values[] = {1, 2, 3, 4, 5, 6};
circular_deque<MoveOnlyInt> first(std::begin(values), std::end(values));
circular_deque<MoveOnlyInt> second(std::move(first));
EXPECT_TRUE(first.empty());
EXPECT_EQ(6u, second.size());
for (int i = 0; i < 6; i++)
EXPECT_EQ(i + 1, second[i].data());
}
TEST(CircularDeque, InitializerListConstructor) {
circular_deque<int> empty({});
ASSERT_TRUE(empty.empty());
circular_deque<int> first({1, 2, 3, 4, 5, 6});
EXPECT_EQ(6u, first.size());
for (int i = 0; i < 6; i++)
EXPECT_EQ(i + 1, first[i]);
}
TEST(CircularDeque, Destructor) {
int destruct_count = 0;
// Contiguous buffer.
{
circular_deque<DestructorCounter> q;
q.resize(5, DestructorCounter(&destruct_count));
EXPECT_EQ(1, destruct_count); // The temporary in the call to resize().
destruct_count = 0;
}
EXPECT_EQ(5, destruct_count); // One call for each.
// Force a wraparound buffer.
{
circular_deque<DestructorCounter> q;
q.reserve(7);
q.resize(5, DestructorCounter(&destruct_count));
// Cycle throught some elements in our buffer to force a wraparound.
destruct_count = 0;
for (int i = 0; i < 4; i++) {
q.emplace_back(&destruct_count);
q.pop_front();
}
EXPECT_EQ(4, destruct_count); // One for each cycle.
destruct_count = 0;
}
EXPECT_EQ(5, destruct_count); // One call for each.
}
TEST(CircularDeque, EqualsCopy) {
circular_deque<int> first = {1, 2, 3, 4, 5, 6};
circular_deque<int> copy;
EXPECT_TRUE(copy.empty());
copy = first;
EXPECT_EQ(6u, copy.size());
for (int i = 0; i < 6; i++) {
EXPECT_EQ(i + 1, first[i]);
EXPECT_EQ(i + 1, copy[i]);
EXPECT_NE(&first[i], &copy[i]);
}
}
TEST(CircularDeque, EqualsMove) {
circular_deque<int> first = {1, 2, 3, 4, 5, 6};
circular_deque<int> move;
EXPECT_TRUE(move.empty());
move = std::move(first);
EXPECT_TRUE(first.empty());
EXPECT_EQ(6u, move.size());
for (int i = 0; i < 6; i++)
EXPECT_EQ(i + 1, move[i]);
}
TEST(CircularDeque, EqualsInitializerList) {
circular_deque<int> q;
EXPECT_TRUE(q.empty());
q = {1, 2, 3, 4, 5, 6};
EXPECT_EQ(6u, q.size());
for (int i = 0; i < 6; i++)
EXPECT_EQ(i + 1, q[i]);
}
TEST(CircularDeque, AssignCountValue) {
circular_deque<int> empty;
empty.assign(0, 52);
EXPECT_EQ(0u, empty.size());
circular_deque<int> full;
size_t count = 13;
int value = 12345;
full.assign(count, value);
EXPECT_EQ(count, full.size());
for (size_t i = 0; i < count; i++)
EXPECT_EQ(value, full[i]);
}
TEST(CircularDeque, AssignIterator) {
int range[8] = {11, 12, 13, 14, 15, 16, 17, 18};
circular_deque<int> empty;
empty.assign(std::begin(range), std::begin(range));
EXPECT_TRUE(empty.empty());
circular_deque<int> full;
full.assign(std::begin(range), std::end(range));
EXPECT_EQ(8u, full.size());
for (size_t i = 0; i < 8; i++)
EXPECT_EQ(range[i], full[i]);
}
TEST(CircularDeque, AssignInitializerList) {
circular_deque<int> empty;
empty.assign({});
EXPECT_TRUE(empty.empty());
circular_deque<int> full;
full.assign({11, 12, 13, 14, 15, 16, 17, 18});
EXPECT_EQ(8u, full.size());
for (int i = 0; i < 8; i++)
EXPECT_EQ(11 + i, full[i]);
}
// Tests [] and .at().
TEST(CircularDeque, At) {
circular_deque<int> q = MakeSequence(10);
CycleTest(q, [](const circular_deque<int>& q, size_t cycle) {
size_t expected_size = 10;
EXPECT_EQ(expected_size, q.size());
// A sequence of 0's.
size_t index = 0;
size_t num_zeros = std::min(expected_size, cycle);
for (size_t i = 0; i < num_zeros; i++, index++) {
EXPECT_EQ(0, q[index]);
EXPECT_EQ(0, q.at(index));
}
// Followed by a sequence of increasing ints.
size_t num_ints = expected_size - num_zeros;
for (int i = 0; i < static_cast<int>(num_ints); i++, index++) {
EXPECT_EQ(i, q[index]);
EXPECT_EQ(i, q.at(index));
}
});
}
// This also tests the copy constructor with lots of different types of
// input configurations.
TEST(CircularDeque, FrontBackPushPop) {
circular_deque<int> q = MakeSequence(10);
int expected_front = 0;
int expected_back = 9;
// Go in one direction.
for (int i = 0; i < 100; i++) {
const circular_deque<int> const_q(q);
EXPECT_EQ(expected_front, q.front());
EXPECT_EQ(expected_back, q.back());
EXPECT_EQ(expected_front, const_q.front());
EXPECT_EQ(expected_back, const_q.back());
expected_front++;
expected_back++;
q.pop_front();
q.push_back(expected_back);
}
// Go back in reverse.
for (int i = 0; i < 100; i++) {
const circular_deque<int> const_q(q);
EXPECT_EQ(expected_front, q.front());
EXPECT_EQ(expected_back, q.back());
EXPECT_EQ(expected_front, const_q.front());
EXPECT_EQ(expected_back, const_q.back());
expected_front--;
expected_back--;
q.pop_back();
q.push_front(expected_front);
}
}
TEST(CircularDeque, ReallocateWithSplitBuffer) {
// Tests reallocating a deque with an internal buffer that looks like this:
// 4 5 x x 0 1 2 3
// end-^ ^-begin
circular_deque<int> q;
q.reserve(7); // Internal buffer is always 1 larger than requested.
q.push_back(-1);
q.push_back(-1);
q.push_back(-1);
q.push_back(-1);
q.push_back(0);
q.pop_front();
q.pop_front();
q.pop_front();
q.pop_front();
q.push_back(1);
q.push_back(2);
q.push_back(3);
q.push_back(4);
q.push_back(5);
q.shrink_to_fit();
EXPECT_EQ(6u, q.size());
EXPECT_EQ(0, q[0]);
EXPECT_EQ(1, q[1]);
EXPECT_EQ(2, q[2]);
EXPECT_EQ(3, q[3]);
EXPECT_EQ(4, q[4]);
EXPECT_EQ(5, q[5]);
}
TEST(CircularDeque, Swap) {
circular_deque<int> a = MakeSequence(10);
circular_deque<int> b = MakeSequence(100);
a.swap(b);
EXPECT_EQ(100u, a.size());
for (int i = 0; i < 100; i++)
EXPECT_EQ(i, a[i]);
EXPECT_EQ(10u, b.size());
for (int i = 0; i < 10; i++)
EXPECT_EQ(i, b[i]);
}
TEST(CircularDeque, Iteration) {
circular_deque<int> q = MakeSequence(10);
int expected_front = 0;
int expected_back = 9;
// This loop causes various combinations of begin and end to be tested.
for (int i = 0; i < 30; i++) {
// Range-based for loop going forward.
int current_expected = expected_front;
for (int cur : q) {
EXPECT_EQ(current_expected, cur);
current_expected++;
}
// Manually test reverse iterators.
current_expected = expected_back;
for (auto cur = q.crbegin(); cur < q.crend(); cur++) {
EXPECT_EQ(current_expected, *cur);
current_expected--;
}
expected_front++;
expected_back++;
q.pop_front();
q.push_back(expected_back);
}
// Go back in reverse.
for (int i = 0; i < 100; i++) {
const circular_deque<int> const_q(q);
EXPECT_EQ(expected_front, q.front());
EXPECT_EQ(expected_back, q.back());
EXPECT_EQ(expected_front, const_q.front());
EXPECT_EQ(expected_back, const_q.back());
expected_front--;
expected_back--;
q.pop_back();
q.push_front(expected_front);
}
}
TEST(CircularDeque, IteratorComparisons) {
circular_deque<int> q = MakeSequence(10);
// This loop causes various combinations of begin and end to be tested.
for (int i = 0; i < 30; i++) {
EXPECT_LT(q.begin(), q.end());
EXPECT_LE(q.begin(), q.end());
EXPECT_LE(q.begin(), q.begin());
EXPECT_GT(q.end(), q.begin());
EXPECT_GE(q.end(), q.begin());
EXPECT_GE(q.end(), q.end());
EXPECT_EQ(q.begin(), q.begin());
EXPECT_NE(q.begin(), q.end());
q.push_front(10);
q.pop_back();
}
}
TEST(CircularDeque, IteratorIncDec) {
circular_deque<int> q = MakeSequence(10);
// Mutable preincrement, predecrement.
{
circular_deque<int>::iterator it = q.begin();
circular_deque<int>::iterator op_result = ++it;
EXPECT_EQ(1, *op_result);
EXPECT_EQ(1, *it);
op_result = --it;
EXPECT_EQ(0, *op_result);
EXPECT_EQ(0, *it);
}
// Const preincrement, predecrement.
{
circular_deque<int>::const_iterator it = q.begin();
circular_deque<int>::const_iterator op_result = ++it;
EXPECT_EQ(1, *op_result);
EXPECT_EQ(1, *it);
op_result = --it;
EXPECT_EQ(0, *op_result);
EXPECT_EQ(0, *it);
}
// Mutable postincrement, postdecrement.
{
circular_deque<int>::iterator it = q.begin();
circular_deque<int>::iterator op_result = it++;
EXPECT_EQ(0, *op_result);
EXPECT_EQ(1, *it);
op_result = it--;
EXPECT_EQ(1, *op_result);
EXPECT_EQ(0, *it);
}
// Const postincrement, postdecrement.
{
circular_deque<int>::const_iterator it = q.begin();
circular_deque<int>::const_iterator op_result = it++;
EXPECT_EQ(0, *op_result);
EXPECT_EQ(1, *it);
op_result = it--;
EXPECT_EQ(1, *op_result);
EXPECT_EQ(0, *it);
}
}
TEST(CircularDeque, IteratorIntegerOps) {
circular_deque<int> q = MakeSequence(10);
int expected_front = 0;
int expected_back = 9;
for (int i = 0; i < 30; i++) {
EXPECT_EQ(0, q.begin() - q.begin());
EXPECT_EQ(0, q.end() - q.end());
EXPECT_EQ(q.size(), static_cast<size_t>(q.end() - q.begin()));
// +=
circular_deque<int>::iterator eight = q.begin();
eight += 8;
EXPECT_EQ(8, eight - q.begin());
EXPECT_EQ(expected_front + 8, *eight);
// -=
eight -= 8;
EXPECT_EQ(q.begin(), eight);
// +
eight = eight + 8;
EXPECT_EQ(8, eight - q.begin());
// -
eight = eight - 8;
EXPECT_EQ(q.begin(), eight);
expected_front++;
expected_back++;
q.pop_front();
q.push_back(expected_back);
}
}
TEST(CircularDeque, CapacityReserveShrink) {
circular_deque<int> q;
// A default constructed queue should have no capacity since it should waste
// no space.
EXPECT_TRUE(q.empty());
EXPECT_EQ(0u, q.size());
EXPECT_EQ(0u, q.capacity());
size_t new_capacity = 100;
q.reserve(new_capacity);
EXPECT_EQ(new_capacity, q.capacity());
// Adding that many items should not cause a resize.
for (size_t i = 0; i < new_capacity; i++)
q.push_back(i);
EXPECT_EQ(new_capacity, q.size());
EXPECT_EQ(new_capacity, q.capacity());
// Shrinking doesn't resize capacity.
size_t capacity_2 = new_capacity / 2;
q.resize(capacity_2);
EXPECT_EQ(capacity_2, q.size());
EXPECT_EQ(new_capacity, q.capacity());
// Shrink to fit to that size.
q.shrink_to_fit();
EXPECT_EQ(capacity_2, q.size());
EXPECT_EQ(capacity_2, q.capacity());
// Shrink to 0, should have the same capacity.
q.resize(0);
EXPECT_EQ(0u, q.size());
EXPECT_EQ(capacity_2, q.capacity());
// Shrinking to fit should give 0 capacity.
q.shrink_to_fit();
EXPECT_EQ(0u, q.size());
EXPECT_EQ(0u, q.capacity());
}
TEST(CircularDeque, ClearAndEmpty) {
circular_deque<int> q;
EXPECT_TRUE(q.empty());
q.resize(10);
EXPECT_EQ(10u, q.size());
EXPECT_FALSE(q.empty());
q.clear();
EXPECT_EQ(0u, q.size());
EXPECT_TRUE(q.empty());
// clear() also should reset the capacity.
EXPECT_EQ(0u, q.capacity());
}
TEST(CircularDeque, Resize) {
circular_deque<int> q;
// Resize with default constructor.
size_t first_size = 10;
q.resize(first_size);
EXPECT_EQ(first_size, q.size());
for (size_t i = 0; i < first_size; i++)
EXPECT_EQ(0, q[i]);
// Resize with different value.
size_t second_expand = 10;
q.resize(first_size + second_expand, 3);
EXPECT_EQ(first_size + second_expand, q.size());
for (size_t i = 0; i < first_size; i++)
EXPECT_EQ(0, q[i]);
for (size_t i = 0; i < second_expand; i++)
EXPECT_EQ(3, q[i + first_size]);
// Erase from the end and add to the beginning so resize is forced to cross
// a circular buffer wrap boundary.
q.shrink_to_fit();
for (int i = 0; i < 5; i++) {
q.pop_back();
q.push_front(6);
}
q.resize(10);
EXPECT_EQ(6, q[0]);
EXPECT_EQ(6, q[1]);
EXPECT_EQ(6, q[2]);
EXPECT_EQ(6, q[3]);
EXPECT_EQ(6, q[4]);
EXPECT_EQ(0, q[5]);
EXPECT_EQ(0, q[6]);
EXPECT_EQ(0, q[7]);
EXPECT_EQ(0, q[8]);
EXPECT_EQ(0, q[9]);
}
// Tests destructor behavior of resize.
TEST(CircularDeque, ResizeDelete) {
int counter = 0;
circular_deque<DestructorCounter> q;
q.resize(10, DestructorCounter(&counter));
// The one temporary when calling resize() should be deleted, that's it.
EXPECT_EQ(1, counter);
// The loops below assume the capacity will be set by resize().
EXPECT_EQ(10u, q.capacity());
counter = 0;
q.resize(5, DestructorCounter(&counter));
// 5 deleted ones + the one temporary in the resize() call.
EXPECT_EQ(6, counter);
// Cycle through some items so the 5 remaining items will cross the boundary
// in the 11-item buffer (one more than the capacity).
counter = 0;
for (int i = 0; i < 7; i++) {
q.emplace_back(&counter);
q.pop_front();
}
EXPECT_EQ(7, counter); // Loop should have deleted 7 items.
counter = 0;
q.resize(1, DestructorCounter(&counter));
// 4 deleted ones + the one temporary in the resize() call.
EXPECT_EQ(5, counter);
}
/*
This test should assert in a debug build. It tries to dereference an iterator
after mutating the container. Uncomment to double-check that this works.
TEST(CircularDeque, UseIteratorAfterMutate) {
circular_deque<int> q;
q.push_back(0);
auto old_begin = q.begin();
EXPECT_EQ(0, *old_begin);
q.push_back(1);
EXPECT_EQ(0, *old_begin); // Should DCHECK.
}
*/
} // namespace base
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_CONTAINERS_QUEUE_H_
#define BASE_CONTAINERS_QUEUE_H_
#include <queue>
#include "base/containers/circular_deque.h"
namespace base {
// Provides a definition of base::queue that's like std::queue but uses a
// base::circular_queue instead. Since std::queue is just a wraper for an
// underlying type, we can just provide a typedef for it that defaults to the
// base circular_deque.
template <class T, class Container = circular_deque<T>>
using queue = std::queue<T, Container>;
} // namespace base
#endif // BASE_CONTAINERS_QUEUE_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_CONTAINERS_VECTOR_BUFFERS_H_
#define BASE_CONTAINERS_VECTOR_BUFFERS_H_
#include <stdlib.h>
#include <string.h>
#include <type_traits>
#include <utility>
#include "base/logging.h"
#include "base/macros.h"
namespace base {
namespace internal {
// Internal implementation detail of base/containers.
//
// Implements a vector-like buffer that holds a certain capacity of T. Unlike
// std::vector, VectorBuffer never constructs or destructs its arguments, and
// can't change sizes. But it does implement templates to assist in efficient
// moving and destruction of those items manually.
//
// In particular, the destructor function does not iterate over the items if
// there is no destructor. Moves should be implemented as a memcpy/memmove for
// trivially copyable objects (POD) otherwise, it should be a std::move if
// possible, and as a last resort it falls back to a copy. This behavior is
// similar to std::vector.
//
// No special consideration is done for noexcept move constructors since
// we compile without exceptions.
//
// The current API does not support moving overlapping ranges.
template <typename T>
class VectorBuffer {
public:
VectorBuffer() {}
VectorBuffer(size_t count)
: buffer_(reinterpret_cast<T*>(malloc(sizeof(T) * count))),
capacity_(count) {}
VectorBuffer(VectorBuffer&& other) noexcept
: buffer_(other.buffer_), capacity_(other.capacity_) {
other.buffer_ = nullptr;
other.capacity_ = 0;
}
~VectorBuffer() { free(buffer_); }
VectorBuffer& operator=(VectorBuffer&& other) {
free(buffer_);
buffer_ = other.buffer_;
capacity_ = other.capacity_;
other.buffer_ = nullptr;
other.capacity_ = 0;
return *this;
}
size_t capacity() const { return capacity_; }
T& operator[](size_t i) { return buffer_[i]; }
const T& operator[](size_t i) const { return buffer_[i]; }
T* begin() { return buffer_; }
T* end() { return &buffer_[capacity_]; }
// DestructRange ------------------------------------------------------------
// Trivially destructible objects need not have their destructors called.
template <typename T2 = T,
typename std::enable_if<std::is_trivially_destructible<T2>::value,
int>::type = 0>
void DestructRange(T* begin, T* end) {}
// Non-trivially destructible objects must have their destructors called
// individually.
template <typename T2 = T,
typename std::enable_if<!std::is_trivially_destructible<T2>::value,
int>::type = 0>
void DestructRange(T* begin, T* end) {
while (begin != end) {
begin->~T();
begin++;
}
}
// MoveRange ----------------------------------------------------------------
//
// The destructor will be called (as necessary) for all moved types. The
// ranges must not overlap.
//
// The parameters and begin and end (one past the last) of the input buffer,
// and the address of the first element to copy to. There must be sufficient
// room in the destination for all items in the range [begin, end).
// Trivially copyable types can use memcpy. trivially copyable implies
// that there is a trivial destructor as we don't have to call it.
template <typename T2 = T,
typename std::enable_if<base::is_trivially_copyable<T2>::value,
int>::type = 0>
static void MoveRange(T* from_begin, T* from_end, T* to) {
DCHECK(!RangesOverlap(from_begin, from_end, to));
memcpy(to, from_begin, (from_end - from_begin) * sizeof(T));
}
// Not trivially copyable, but movable: call the move constructor and
// destruct the original.
template <typename T2 = T,
typename std::enable_if<std::is_move_constructible<T2>::value &&
!base::is_trivially_copyable<T2>::value,
int>::type = 0>
static void MoveRange(T* from_begin, T* from_end, T* to) {
DCHECK(!RangesOverlap(from_begin, from_end, to));
while (from_begin != from_end) {
new (to) T(std::move(*from_begin));
from_begin->~T();
from_begin++;
to++;
}
}
// Not movable, not trivially copyable: call the copy constructor and
// destruct the original.
template <typename T2 = T,
typename std::enable_if<!std::is_move_constructible<T2>::value &&
!base::is_trivially_copyable<T2>::value,
int>::type = 0>
static void MoveRange(T* from_begin, T* from_end, T* to) {
DCHECK(!RangesOverlap(from_begin, from_end, to));
while (from_begin != from_end) {
new (to) T(*from_begin);
from_begin->~T();
from_begin++;
to++;
}
}
private:
static bool RangesOverlap(const T* from_begin,
const T* from_end,
const T* to) {
return !(to >= from_end || to + (from_end - from_begin) <= from_begin);
}
T* buffer_ = nullptr;
size_t capacity_ = 0;
DISALLOW_COPY_AND_ASSIGN(VectorBuffer);
};
} // namespace internal
} // namespace base
#endif // BASE_CONTAINERS_VECTOR_BUFFERS_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/containers/vector_buffer.h"
#include "base/test/copy_only_int.h"
#include "base/test/move_only_int.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace internal {
TEST(VectorBuffer, DeletePOD) {
constexpr int size = 10;
VectorBuffer<int> buffer(size);
for (int i = 0; i < size; i++)
buffer.begin()[i] = i + 1;
buffer.DestructRange(buffer.begin(), buffer.end());
// Delete should do nothing.
for (int i = 0; i < size; i++)
EXPECT_EQ(i + 1, buffer.begin()[i]);
}
TEST(VectorBuffer, DeleteMoveOnly) {
constexpr int size = 10;
VectorBuffer<MoveOnlyInt> buffer(size);
for (int i = 0; i < size; i++)
new (buffer.begin() + i) MoveOnlyInt(i + 1);
buffer.DestructRange(buffer.begin(), buffer.end());
// Delete should have reset all of the values to 0.
for (int i = 0; i < size; i++)
EXPECT_EQ(0, buffer.begin()[i].data());
}
TEST(VectorBuffer, PODMove) {
constexpr int size = 10;
VectorBuffer<int> dest(size);
VectorBuffer<int> original(size);
for (int i = 0; i < size; i++)
original.begin()[i] = i + 1;
original.MoveRange(original.begin(), original.end(), dest.begin());
for (int i = 0; i < size; i++)
EXPECT_EQ(i + 1, dest.begin()[i]);
}
TEST(VectorBuffer, MovableMove) {
constexpr int size = 10;
VectorBuffer<MoveOnlyInt> dest(size);
VectorBuffer<MoveOnlyInt> original(size);
for (int i = 0; i < size; i++)
new (original.begin() + i) MoveOnlyInt(i + 1);
original.MoveRange(original.begin(), original.end(), dest.begin());
// Moving from a MoveOnlyInt resets to 0.
for (int i = 0; i < size; i++) {
EXPECT_EQ(0, original.begin()[i].data());
EXPECT_EQ(i + 1, dest.begin()[i].data());
}
}
TEST(VectorBuffer, CopyToMove) {
constexpr int size = 10;
VectorBuffer<CopyOnlyInt> dest(size);
VectorBuffer<CopyOnlyInt> original(size);
for (int i = 0; i < size; i++)
new (original.begin() + i) CopyOnlyInt(i + 1);
original.MoveRange(original.begin(), original.end(), dest.begin());
// The original should have been destructed, which should reset the value to
// 0. Technically this dereferences the destructed object.
for (int i = 0; i < size; i++) {
EXPECT_EQ(0, original.begin()[i].data());
EXPECT_EQ(i + 1, dest.begin()[i].data());
}
}
} // namespace internal
} // namespace base
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <stddef.h> #include <stddef.h>
#include <iosfwd> #include <iosfwd>
#include <iterator>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
...@@ -42,18 +43,24 @@ template <class T> struct is_non_const_reference<const T&> : std::false_type {}; ...@@ -42,18 +43,24 @@ template <class T> struct is_non_const_reference<const T&> : std::false_type {};
namespace internal { namespace internal {
// Implementation detail of base::void_t below.
template <typename...> template <typename...>
struct make_void { struct make_void {
using type = void; using type = void;
}; };
// A clone of C++17 std::void_t. } // namespace internal
// Unlike the original version, we need |make_void| as a helper struct to avoid
// a C++14 defect. // base::void_t is an implementation of std::void_t from C++17.
// ref: http://en.cppreference.com/w/cpp/types/void_t //
// ref: http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1558 // We use |base::internal::make_void| as a helper struct to avoid a C++14
// defect:
// http://en.cppreference.com/w/cpp/types/void_t
// http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1558
template <typename... Ts> template <typename... Ts>
using void_t = typename make_void<Ts...>::type; using void_t = typename ::base::internal::make_void<Ts...>::type;
namespace internal {
// Uses expression SFINAE to detect whether using operator<< would work. // Uses expression SFINAE to detect whether using operator<< would work.
template <typename T, typename = void> template <typename T, typename = void>
...@@ -64,6 +71,17 @@ struct SupportsOstreamOperator<T, ...@@ -64,6 +71,17 @@ struct SupportsOstreamOperator<T,
<< std::declval<T>()))> << std::declval<T>()))>
: std::true_type {}; : std::true_type {};
// Used to detech whether the given type is an iterator. This is normally used
// with std::enable_if to provide disambiguation for functions that take
// templatzed iterators as input.
template <typename T, typename = void>
struct is_iterator : std::false_type {};
template <typename T>
struct is_iterator<T,
void_t<typename std::iterator_traits<T>::iterator_category>>
: std::true_type {};
} // namespace internal } // namespace internal
// is_trivially_copyable is especially hard to get right. // is_trivially_copyable is especially hard to get right.
......
...@@ -30,6 +30,7 @@ static_library("test_support") { ...@@ -30,6 +30,7 @@ static_library("test_support") {
"android/java_handler_thread_for_testing.h", "android/java_handler_thread_for_testing.h",
"android/test_system_message_handler_link_android.cc", "android/test_system_message_handler_link_android.cc",
"android/test_system_message_handler_link_android.h", "android/test_system_message_handler_link_android.h",
"copy_only_int.h",
"fuzzed_data_provider.cc", "fuzzed_data_provider.cc",
"fuzzed_data_provider.h", "fuzzed_data_provider.h",
"gtest_util.cc", "gtest_util.cc",
...@@ -57,6 +58,7 @@ static_library("test_support") { ...@@ -57,6 +58,7 @@ static_library("test_support") {
"mock_entropy_provider.h", "mock_entropy_provider.h",
"mock_log.cc", "mock_log.cc",
"mock_log.h", "mock_log.h",
"move_only_int.h",
"multiprocess_test.cc", "multiprocess_test.cc",
"multiprocess_test.h", "multiprocess_test.h",
"multiprocess_test_android.cc", "multiprocess_test_android.cc",
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef BASE_TEST_COPY_ONLY_INT_H_
#define BASE_TEST_COPY_ONLY_INT_H_
#include "base/macros.h"
namespace base {
// A copy-only (not moveable) class that holds an integer. This is designed for
// testing containers. See also MoveOnlyInt.
class CopyOnlyInt {
public:
explicit CopyOnlyInt(int data = 1) : data_(data) {}
CopyOnlyInt(const CopyOnlyInt& other) : data_(other.data_) {}
~CopyOnlyInt() { data_ = 0; }
friend bool operator==(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
return lhs.data_ == rhs.data_;
}
friend bool operator!=(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
return !operator==(lhs, rhs);
}
friend bool operator<(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
return lhs.data_ < rhs.data_;
}
friend bool operator>(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
return rhs < lhs;
}
friend bool operator<=(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
return !(rhs < lhs);
}
friend bool operator>=(const CopyOnlyInt& lhs, const CopyOnlyInt& rhs) {
return !(lhs < rhs);
}
int data() const { return data_; }
private:
int data_;
CopyOnlyInt(CopyOnlyInt&&) = delete;
CopyOnlyInt& operator=(CopyOnlyInt&) = delete;
};
} // namespace base
#endif // BASE_TEST_COPY_ONLY_INT_H_
...@@ -2,20 +2,21 @@ ...@@ -2,20 +2,21 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#ifndef BASE_CONTAINERS_CONTAINER_TEST_UTILS_H_ #ifndef BASE_TEST_MOVE_ONLY_INT_H_
#define BASE_CONTAINERS_CONTAINER_TEST_UTILS_H_ #define BASE_TEST_MOVE_ONLY_INT_H_
// This file contains some helper classes for testing conainer behavior.
#include "base/macros.h" #include "base/macros.h"
namespace base { namespace base {
// A move-only class that holds an integer. // A move-only class that holds an integer. This is designed for testing
// containers. See also CopyOnlyInt.
class MoveOnlyInt { class MoveOnlyInt {
public: public:
explicit MoveOnlyInt(int data = 1) : data_(data) {} explicit MoveOnlyInt(int data = 1) : data_(data) {}
MoveOnlyInt(MoveOnlyInt&& other) : data_(other.data_) { other.data_ = 0; } MoveOnlyInt(MoveOnlyInt&& other) : data_(other.data_) { other.data_ = 0; }
~MoveOnlyInt() { data_ = 0; }
MoveOnlyInt& operator=(MoveOnlyInt&& other) { MoveOnlyInt& operator=(MoveOnlyInt&& other) {
data_ = other.data_; data_ = other.data_;
other.data_ = 0; other.data_ = 0;
...@@ -64,4 +65,4 @@ class MoveOnlyInt { ...@@ -64,4 +65,4 @@ class MoveOnlyInt {
} // namespace base } // namespace base
#endif // BASE_CONTAINERS_CONTAINER_TEST_UTILS_H_ #endif // BASE_TEST_MOVE_ONLY_INT_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment