From e2a83eee7337e5a8244c7abacc552bf5ef344442 Mon Sep 17 00:00:00 2001 From: Karl Wiberg Date: Mon, 26 Oct 2015 19:51:29 +0100 Subject: [PATCH] Introduce rtc::ArrayView, which keeps track of an array that it doesn't own The main intended use case is as a function argument, replacing the harder-to-read and harder-to-use separate pointer and size arguments. It's easier to read because it's just one argument instead of two, and with clearly defined semantics; it's easier to use because it has iterators, and will automatically figure out the size of arrays. BUG=webrtc:5028 R=andrew@webrtc.org, solenberg@webrtc.org Review URL: https://codereview.webrtc.org/1408403002 . Cr-Commit-Position: refs/heads/master@{#10415} --- webrtc/base/BUILD.gn | 1 + webrtc/base/array_view.h | 84 +++++++++++ webrtc/base/array_view_unittest.cc | 217 +++++++++++++++++++++++++++++ webrtc/base/base.gyp | 1 + webrtc/base/base_tests.gyp | 1 + webrtc/base/checks.h | 2 + 6 files changed, 306 insertions(+) create mode 100644 webrtc/base/array_view.h create mode 100644 webrtc/base/array_view_unittest.cc diff --git a/webrtc/base/BUILD.gn b/webrtc/base/BUILD.gn index 916a801700..11a26646be 100644 --- a/webrtc/base/BUILD.gn +++ b/webrtc/base/BUILD.gn @@ -94,6 +94,7 @@ static_library("rtc_base_approved") { public_configs = [ "..:common_inherited_config" ] sources = [ + "array_view.h", "atomicops.h", "bitbuffer.cc", "bitbuffer.h", diff --git a/webrtc/base/array_view.h b/webrtc/base/array_view.h new file mode 100644 index 0000000000..019bd8b6c6 --- /dev/null +++ b/webrtc/base/array_view.h @@ -0,0 +1,84 @@ +/* + * Copyright 2015 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_BASE_ARRAY_VIEW_H_ +#define WEBRTC_BASE_ARRAY_VIEW_H_ + +#include + +#include "webrtc/base/checks.h" + +namespace rtc { + +// Keeps track of an array (a pointer and a size) that it doesn't own. +// ArrayView objects are immutable except for assignment, and small enough to +// be cheaply passed by value. +// +// Note that ArrayView and ArrayView are distinct types; this is +// how you would represent mutable and unmutable views of an array. +template +class ArrayView final { + public: + // Construct an empty ArrayView. + ArrayView() : ArrayView(static_cast(nullptr), 0) {} + + // Construct an ArrayView for a (pointer,size) pair. + template + ArrayView(U* data, size_t size) + : data_(size == 0 ? nullptr : data), size_(size) { + CheckInvariant(); + } + + // Construct an ArrayView for an array. + template + ArrayView(U (&array)[N]) : ArrayView(&array[0], N) {} + + // Construct an ArrayView for any type U that has a size() method whose + // return value converts implicitly to size_t, and a data() method whose + // return value converts implicitly to T*. In particular, this means we allow + // conversion from ArrayView to ArrayView, but not the other way + // around. Other allowed conversions include std::vector to ArrayView + // or ArrayView, const std::vector to ArrayView, and + // rtc::Buffer to ArrayView (with the same const behavior as + // std::vector). + template + ArrayView(U& u) : ArrayView(u.data(), u.size()) {} + // TODO(kwiberg): Remove the special case for std::vector (and the include of + // ); it is handled by the general case in C++11, since std::vector + // has a data() method there. + template + ArrayView(std::vector& u) + : ArrayView(u.empty() ? nullptr : &u[0], u.size()) {} + + // Indexing, size, and iteration. These allow mutation even if the ArrayView + // is const, because the ArrayView doesn't own the array. (To prevent + // mutation, use ArrayView.) + size_t size() const { return size_; } + T* data() const { return data_; } + T& operator[](size_t idx) const { + RTC_DCHECK_LT(idx, size_); + RTC_DCHECK(data_); // Follows from size_ > idx and the class invariant. + return data_[idx]; + } + T* begin() const { return data_; } + T* end() const { return data_ + size_; } + const T* cbegin() const { return data_; } + const T* cend() const { return data_ + size_; } + + private: + // Invariant: !data_ iff size_ == 0. + void CheckInvariant() const { RTC_DCHECK_EQ(!data_, size_ == 0); } + T* data_; + size_t size_; +}; + +} // namespace rtc + +#endif // WEBRTC_BASE_ARRAY_VIEW_H_ diff --git a/webrtc/base/array_view_unittest.cc b/webrtc/base/array_view_unittest.cc new file mode 100644 index 0000000000..0d1bff03d1 --- /dev/null +++ b/webrtc/base/array_view_unittest.cc @@ -0,0 +1,217 @@ +/* + * Copyright 2015 The WebRTC Project Authors. All rights reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include +#include +#include + +#include "webrtc/base/array_view.h" +#include "webrtc/base/buffer.h" +#include "webrtc/base/checks.h" +#include "webrtc/base/gunit.h" + +namespace rtc { + +namespace { +template +void Call(ArrayView) {} +} // namespace + +TEST(ArrayViewTest, TestConstructFromPtrAndArray) { + char arr[] = "Arrr!"; + const char carr[] = "Carrr!"; + Call(arr); + Call(carr); + Call(arr); + // Call(carr); // Compile error, because can't drop const. + // Call(arr); // Compile error, because incompatible types. + ArrayView x; + EXPECT_EQ(0u, x.size()); + EXPECT_EQ(nullptr, x.data()); + ArrayView y = arr; + EXPECT_EQ(6u, y.size()); + EXPECT_EQ(arr, y.data()); + ArrayView z(arr + 1, 3); + EXPECT_EQ(3u, z.size()); + EXPECT_EQ(arr + 1, z.data()); + ArrayView w(arr, 2); + EXPECT_EQ(2u, w.size()); + EXPECT_EQ(arr, w.data()); + ArrayView q(arr, 0); + EXPECT_EQ(0u, q.size()); + EXPECT_EQ(nullptr, q.data()); +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) + // DCHECK error (nullptr with nonzero size). + EXPECT_DEATH(ArrayView(static_cast(nullptr), 5), ""); +#endif + // These are compile errors, because incompatible types. + // ArrayView m = arr; + // ArrayView n(arr + 2, 2); +} + +TEST(ArrayViewTest, TestCopyConstructor) { + char arr[] = "Arrr!"; + ArrayView x = arr; + EXPECT_EQ(6u, x.size()); + EXPECT_EQ(arr, x.data()); + ArrayView y = x; // Copy non-const -> non-const. + EXPECT_EQ(6u, y.size()); + EXPECT_EQ(arr, y.data()); + ArrayView z = x; // Copy non-const -> const. + EXPECT_EQ(6u, z.size()); + EXPECT_EQ(arr, z.data()); + ArrayView w = z; // Copy const -> const. + EXPECT_EQ(6u, w.size()); + EXPECT_EQ(arr, w.data()); + // ArrayView v = z; // Compile error, because can't drop const. +} + +TEST(ArrayViewTest, TestCopyAssignment) { + char arr[] = "Arrr!"; + ArrayView x(arr); + EXPECT_EQ(6u, x.size()); + EXPECT_EQ(arr, x.data()); + ArrayView y; + y = x; // Copy non-const -> non-const. + EXPECT_EQ(6u, y.size()); + EXPECT_EQ(arr, y.data()); + ArrayView z; + z = x; // Copy non-const -> const. + EXPECT_EQ(6u, z.size()); + EXPECT_EQ(arr, z.data()); + ArrayView w; + w = z; // Copy const -> const. + EXPECT_EQ(6u, w.size()); + EXPECT_EQ(arr, w.data()); + // ArrayView v; + // v = z; // Compile error, because can't drop const. +} + +TEST(ArrayViewTest, TestStdVector) { + std::vector v; + v.push_back(3); + v.push_back(11); + Call(v); + Call(v); + // Call(v); // Compile error, because incompatible types. + ArrayView x = v; + EXPECT_EQ(2u, x.size()); + EXPECT_EQ(v.data(), x.data()); + ArrayView y; + y = v; + EXPECT_EQ(2u, y.size()); + EXPECT_EQ(v.data(), y.data()); + // ArrayView d = v; // Compile error, because incompatible types. + const std::vector cv; + Call(cv); + // Call(cv); // Compile error, because can't drop const. + ArrayView z = cv; + EXPECT_EQ(0u, z.size()); + EXPECT_EQ(nullptr, z.data()); + // ArrayView w = cv; // Compile error, because can't drop const. +} + +TEST(ArrayViewTest, TestRtcBuffer) { + rtc::Buffer b = "so buffer"; + Call(b); + Call(b); + // Call(b); // Compile error, because incompatible types. + ArrayView x = b; + EXPECT_EQ(10u, x.size()); + EXPECT_EQ(b.data(), x.data()); + ArrayView y; + y = b; + EXPECT_EQ(10u, y.size()); + EXPECT_EQ(b.data(), y.data()); + // ArrayView d = b; // Compile error, because incompatible types. + const rtc::Buffer cb = "very const"; + Call(cb); + // Call(cb); // Compile error, because can't drop const. + ArrayView z = cb; + EXPECT_EQ(11u, z.size()); + EXPECT_EQ(cb.data(), z.data()); + // ArrayView w = cb; // Compile error, because can't drop const. +} + +TEST(ArrayViewTest, TestSwap) { + const char arr[] = "Arrr!"; + const char aye[] = "Aye, Cap'n!"; + ArrayView x(arr); + EXPECT_EQ(6u, x.size()); + EXPECT_EQ(arr, x.data()); + ArrayView y(aye); + EXPECT_EQ(12u, y.size()); + EXPECT_EQ(aye, y.data()); + using std::swap; + swap(x, y); + EXPECT_EQ(12u, x.size()); + EXPECT_EQ(aye, x.data()); + EXPECT_EQ(6u, y.size()); + EXPECT_EQ(arr, y.data()); + // ArrayView z; + // swap(x, z); // Compile error, because can't drop const. +} + +TEST(ArrayViewTest, TestIndexing) { + char arr[] = "abcdefg"; + ArrayView x(arr); + const ArrayView y(arr); + ArrayView z(arr); + EXPECT_EQ(8u, x.size()); + EXPECT_EQ(8u, y.size()); + EXPECT_EQ(8u, z.size()); + EXPECT_EQ('b', x[1]); + EXPECT_EQ('c', y[2]); + EXPECT_EQ('d', z[3]); + x[3] = 'X'; + y[2] = 'Y'; + // z[1] = 'Z'; // Compile error, because z's element type is const char. + EXPECT_EQ('b', x[1]); + EXPECT_EQ('Y', y[2]); + EXPECT_EQ('X', z[3]); +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) + EXPECT_DEATH(z[8], ""); // DCHECK error (index out of bounds). +#endif +} + +TEST(ArrayViewTest, TestIterationEmpty) { + ArrayView>>> av; + EXPECT_FALSE(av.begin()); + EXPECT_FALSE(av.cbegin()); + EXPECT_FALSE(av.end()); + EXPECT_FALSE(av.cend()); + for (auto& e : av) { + EXPECT_TRUE(false); + EXPECT_EQ(42u, e.size()); // Dummy use of e to prevent unused var warning. + } +} + +TEST(ArrayViewTest, TestIteration) { + char arr[] = "Arrr!"; + ArrayView av(arr); + EXPECT_EQ('A', *av.begin()); + EXPECT_EQ('A', *av.cbegin()); + EXPECT_EQ('\0', *(av.end() - 1)); + EXPECT_EQ('\0', *(av.cend() - 1)); + char i = 0; + for (auto& e : av) { + EXPECT_EQ(arr + i, &e); + e = 's' + i; + ++i; + } + i = 0; + for (auto& e : ArrayView(av)) { + EXPECT_EQ(arr + i, &e); + // e = 'q' + i; // Compile error, because e is a const char&. + ++i; + } +} + +} // namespace rtc diff --git a/webrtc/base/base.gyp b/webrtc/base/base.gyp index 0db04389ca..4b6ad85362 100644 --- a/webrtc/base/base.gyp +++ b/webrtc/base/base.gyp @@ -29,6 +29,7 @@ 'target_name': 'rtc_base_approved', 'type': 'static_library', 'sources': [ + 'array_view.h', 'atomicops.h', 'basictypes.h', 'bitbuffer.cc', diff --git a/webrtc/base/base_tests.gyp b/webrtc/base/base_tests.gyp index 2c84036235..4c3dd3dba1 100644 --- a/webrtc/base/base_tests.gyp +++ b/webrtc/base/base_tests.gyp @@ -45,6 +45,7 @@ 'type': 'none', 'direct_dependent_settings': { 'sources': [ + 'array_view_unittest.cc', 'atomicops_unittest.cc', 'autodetectproxy_unittest.cc', 'bandwidthsmoother_unittest.cc', diff --git a/webrtc/base/checks.h b/webrtc/base/checks.h index 98e896a15b..681361a3d2 100644 --- a/webrtc/base/checks.h +++ b/webrtc/base/checks.h @@ -164,6 +164,7 @@ DEFINE_RTC_CHECK_OP_IMPL(GT, > ) // code in debug builds. It does reference the condition parameter in all cases, // though, so callers won't risk getting warnings about unused variables. #if (!defined(NDEBUG) || defined(DCHECK_ALWAYS_ON)) +#define RTC_DCHECK_IS_ON 1 #define RTC_DCHECK(condition) RTC_CHECK(condition) #define RTC_DCHECK_EQ(v1, v2) RTC_CHECK_EQ(v1, v2) #define RTC_DCHECK_NE(v1, v2) RTC_CHECK_NE(v1, v2) @@ -172,6 +173,7 @@ DEFINE_RTC_CHECK_OP_IMPL(GT, > ) #define RTC_DCHECK_GE(v1, v2) RTC_CHECK_GE(v1, v2) #define RTC_DCHECK_GT(v1, v2) RTC_CHECK_GT(v1, v2) #else +#define RTC_DCHECK_IS_ON 0 #define RTC_DCHECK(condition) RTC_EAT_STREAM_PARAMETERS(condition) #define RTC_DCHECK_EQ(v1, v2) RTC_EAT_STREAM_PARAMETERS((v1) == (v2)) #define RTC_DCHECK_NE(v1, v2) RTC_EAT_STREAM_PARAMETERS((v1) != (v2))