diff options
Diffstat (limited to 'absl/memory')
-rw-r--r-- | absl/memory/BUILD.bazel | 47 | ||||
-rw-r--r-- | absl/memory/README.md | 22 | ||||
-rw-r--r-- | absl/memory/memory.h | 622 | ||||
-rw-r--r-- | absl/memory/memory_test.cc | 590 |
4 files changed, 1281 insertions, 0 deletions
diff --git a/absl/memory/BUILD.bazel b/absl/memory/BUILD.bazel new file mode 100644 index 000000000000..91fc55fc460f --- /dev/null +++ b/absl/memory/BUILD.bazel @@ -0,0 +1,47 @@ +# +# Copyright 2017 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +load( + "//absl:copts.bzl", + "ABSL_DEFAULT_COPTS", + "ABSL_TEST_COPTS", +) +load( + "//absl:test_dependencies.bzl", + "GUNIT_MAIN_DEPS_SELECTOR", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) # Apache 2.0 + +cc_library( + name = "memory", + hdrs = ["memory.h"], + copts = ABSL_DEFAULT_COPTS, + deps = ["//absl/meta:type_traits"], +) + +cc_test( + name = "memory_test", + srcs = ["memory_test.cc"], + copts = ABSL_TEST_COPTS, + deps = [ + ":memory", + "//absl/base", + "//absl/base:core_headers", + ] + select(GUNIT_MAIN_DEPS_SELECTOR), +) diff --git a/absl/memory/README.md b/absl/memory/README.md new file mode 100644 index 000000000000..72eddd9c05aa --- /dev/null +++ b/absl/memory/README.md @@ -0,0 +1,22 @@ +# ABSL Memory + +This directory contains packages related to abstractions for managing memory +within objects. + +## Library Listing + +Only one library target exists within this directory at this time: + +* **memory** (`//absl/memory:memory`) provides classes and + utility functions for managing memory associated with pointers. + + +## Memory Library File Listing + +The following header files are directly included within the +`absl::memory` library: + +### Smart Pointer Management + +* `memory.h` + <br/>Pointer memory management abstractions for handling unique pointers diff --git a/absl/memory/memory.h b/absl/memory/memory.h new file mode 100644 index 000000000000..c67996084ef6 --- /dev/null +++ b/absl/memory/memory.h @@ -0,0 +1,622 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: memory.h +// ----------------------------------------------------------------------------- +// +// This header file contains utility functions for managing the creation and +// conversion of smart pointers. This file is an extension to the C++ +// standard <memory> library header file. + +#ifndef ABSL_MEMORY_MEMORY_H_ +#define ABSL_MEMORY_MEMORY_H_ + +#include <cstddef> +#include <limits> +#include <memory> +#include <new> +#include <type_traits> +#include <utility> + +#include "absl/meta/type_traits.h" + +namespace absl { + +// ----------------------------------------------------------------------------- +// Function Template: WrapUnique() +// ----------------------------------------------------------------------------- +// +// Transfers ownership of a raw pointer to a `std::unique_ptr`. The returned +// value is a `std::unique_ptr` of deduced type. +// +// Example: +// X* NewX(int, int); +// auto x = WrapUnique(NewX(1, 2)); // 'x' is std::unique_ptr<X>. +// +// `absl::WrapUnique` is useful for capturing the output of a raw pointer +// factory. However, prefer 'absl::make_unique<T>(args...) over +// 'absl::WrapUnique(new T(args...))'. +// +// auto x = WrapUnique(new X(1, 2)); // works, but nonideal. +// auto x = make_unique<X>(1, 2); // safer, standard, avoids raw 'new'. +// +// Note that `absl::WrapUnique(p)` is valid only if `delete p` is a valid +// expression. In particular, `absl::WrapUnique()` cannot wrap pointers to +// arrays, functions or void, and it must not be used to capture pointers +// obtained from array-new expressions (even though that would compile!). +template <typename T> +std::unique_ptr<T> WrapUnique(T* ptr) { + static_assert(!std::is_array<T>::value, "array types are unsupported"); + static_assert(std::is_object<T>::value, "non-object types are unsupported"); + return std::unique_ptr<T>(ptr); +} + +namespace memory_internal { + +// Traits to select proper overload and return type for `absl::make_unique<>`. +template <typename T> +struct MakeUniqueResult { + using scalar = std::unique_ptr<T>; +}; +template <typename T> +struct MakeUniqueResult<T[]> { + using array = std::unique_ptr<T[]>; +}; +template <typename T, size_t N> +struct MakeUniqueResult<T[N]> { + using invalid = void; +}; + +} // namespace memory_internal + +// ----------------------------------------------------------------------------- +// Function Template: make_unique<T>() +// ----------------------------------------------------------------------------- +// +// Creates a `std::unique_ptr<>`, while avoiding issues creating temporaries +// during the construction process. `absl::make_unique<>` also avoids redundant +// type declarations, by avoiding the need to explicitly use the `new` operator. +// +// This implementation of `absl::make_unique<>` is designed for C++11 code and +// will be replaced in C++14 by the equivalent `std::make_unique<>` abstraction. +// `absl::make_unique<>` is designed to be 100% compatible with +// `std::make_unique<>` so that the eventual migration will involve a simple +// rename operation. +// +// For more background on why `std::unique_ptr<T>(new T(a,b))` is problematic, +// see Herb Sutter's explanation on +// (Exception-Safe Function Calls)[http://herbsutter.com/gotw/_102/]. +// (In general, reviewers should treat `new T(a,b)` with scrutiny.) +// +// Example usage: +// +// auto p = make_unique<X>(args...); // 'p' is a std::unique_ptr<X> +// auto pa = make_unique<X[]>(5); // 'pa' is a std::unique_ptr<X[]> +// +// Three overloads of `absl::make_unique` are required: +// +// - For non-array T: +// +// Allocates a T with `new T(std::forward<Args> args...)`, +// forwarding all `args` to T's constructor. +// Returns a `std::unique_ptr<T>` owning that object. +// +// - For an array of unknown bounds T[]: +// +// `absl::make_unique<>` will allocate an array T of type U[] with +// `new U[n]()` and return a `std::unique_ptr<U[]>` owning that array. +// +// Note that 'U[n]()' is different from 'U[n]', and elements will be +// value-initialized. Note as well that `std::unique_ptr` will perform its +// own destruction of the array elements upon leaving scope, even though +// the array [] does not have a default destructor. +// +// NOTE: an array of unknown bounds T[] may still be (and often will be) +// initialized to have a size, and will still use this overload. E.g: +// +// auto my_array = absl::make_unique<int[]>(10); +// +// - For an array of known bounds T[N]: +// +// `absl::make_unique<>` is deleted (like with `std::make_unique<>`) as +// this overload is not useful. +// +// NOTE: an array of known bounds T[N] is not considered a useful +// construction, and may cause undefined behavior in templates. E.g: +// +// auto my_array = absl::make_unique<int[10]>(); +// +// In those cases, of course, you can still use the overload above and +// simply initialize it to its desired size: +// +// auto my_array = absl::make_unique<int[]>(10); + +// `absl::make_unique` overload for non-array types. +template <typename T, typename... Args> +typename memory_internal::MakeUniqueResult<T>::scalar make_unique( + Args&&... args) { + return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); +} + +// `absl::make_unique` overload for an array T[] of unknown bounds. +// The array allocation needs to use the `new T[size]` form and cannot take +// element constructor arguments. The `std::unique_ptr` will manage destructing +// these array elements. +template <typename T> +typename memory_internal::MakeUniqueResult<T>::array make_unique(size_t n) { + return std::unique_ptr<T>(new typename absl::remove_extent_t<T>[n]()); +} + +// `absl::make_unique` overload for an array T[N] of known bounds. +// This construction will be rejected. +template <typename T, typename... Args> +typename memory_internal::MakeUniqueResult<T>::invalid make_unique( + Args&&... /* args */) = delete; + +// ----------------------------------------------------------------------------- +// Function Template: RawPtr() +// ----------------------------------------------------------------------------- +// +// Extracts the raw pointer from a pointer-like 'ptr'. `absl::RawPtr` is useful +// within templates that need to handle a complement of raw pointers, +// `std::nullptr_t`, and smart pointers. +template <typename T> +auto RawPtr(T&& ptr) -> decltype(&*ptr) { + // ptr is a forwarding reference to support Ts with non-const operators. + return (ptr != nullptr) ? &*ptr : nullptr; +} +inline std::nullptr_t RawPtr(std::nullptr_t) { return nullptr; } + +// ----------------------------------------------------------------------------- +// Function Template: ShareUniquePtr() +// ----------------------------------------------------------------------------- +// +// Transforms a `std::unique_ptr` rvalue into a `std::shared_ptr`. The returned +// value is a `std::shared_ptr` of deduced type and ownership is transferred to +// the shared pointer. +// +// Example: +// +// auto up = absl::make_unique<int>(10); +// auto sp = absl::ShareUniquePtr(std::move(up)); // shared_ptr<int> +// CHECK_EQ(*sp, 10); +// CHECK(up == nullptr); +// +// Note that this conversion is correct even when T is an array type, although +// the resulting shared pointer may not be very useful. +// +// Implements the resolution of [LWG 2415](http://wg21.link/lwg2415), by which a +// null shared pointer does not attempt to call the deleter. +template <typename T, typename D> +std::shared_ptr<T> ShareUniquePtr(std::unique_ptr<T, D>&& ptr) { + return ptr ? std::shared_ptr<T>(std::move(ptr)) : std::shared_ptr<T>(); +} + +// ----------------------------------------------------------------------------- +// Function Template: WeakenPtr() +// ----------------------------------------------------------------------------- +// +// Creates a weak pointer associated with a given shared pointer. The returned +// value is a `std::weak_ptr` of deduced type. +// +// Example: +// +// auto sp = std::make_shared<int>(10); +// auto wp = absl::WeakenPtr(sp); +// CHECK_EQ(sp.get(), wp.lock().get()); +// sp.reset(); +// CHECK(wp.lock() == nullptr); +// +template <typename T> +std::weak_ptr<T> WeakenPtr(const std::shared_ptr<T>& ptr) { + return std::weak_ptr<T>(ptr); +} + +namespace memory_internal { + +// ExtractOr<E, O, D>::type evaluates to E<O> if possible. Otherwise, D. +template <template <typename> class Extract, typename Obj, typename Default, + typename> +struct ExtractOr { + using type = Default; +}; + +template <template <typename> class Extract, typename Obj, typename Default> +struct ExtractOr<Extract, Obj, Default, void_t<Extract<Obj>>> { + using type = Extract<Obj>; +}; + +template <template <typename> class Extract, typename Obj, typename Default> +using ExtractOrT = typename ExtractOr<Extract, Obj, Default, void>::type; + +// Extractors for the features of allocators. +template <typename T> +using GetPointer = typename T::pointer; + +template <typename T> +using GetConstPointer = typename T::const_pointer; + +template <typename T> +using GetVoidPointer = typename T::void_pointer; + +template <typename T> +using GetConstVoidPointer = typename T::const_void_pointer; + +template <typename T> +using GetDifferenceType = typename T::difference_type; + +template <typename T> +using GetSizeType = typename T::size_type; + +template <typename T> +using GetPropagateOnContainerCopyAssignment = + typename T::propagate_on_container_copy_assignment; + +template <typename T> +using GetPropagateOnContainerMoveAssignment = + typename T::propagate_on_container_move_assignment; + +template <typename T> +using GetPropagateOnContainerSwap = typename T::propagate_on_container_swap; + +template <typename T> +using GetIsAlwaysEqual = typename T::is_always_equal; + +template <typename T> +struct GetFirstArg; + +template <template <typename...> class Class, typename T, typename... Args> +struct GetFirstArg<Class<T, Args...>> { + using type = T; +}; + +template <typename Ptr, typename = void> +struct ElementType { + using type = typename GetFirstArg<Ptr>::type; +}; + +template <typename T> +struct ElementType<T, void_t<typename T::element_type>> { + using type = typename T::element_type; +}; + +template <typename T, typename U> +struct RebindFirstArg; + +template <template <typename...> class Class, typename T, typename... Args, + typename U> +struct RebindFirstArg<Class<T, Args...>, U> { + using type = Class<U, Args...>; +}; + +template <typename T, typename U, typename = void> +struct RebindPtr { + using type = typename RebindFirstArg<T, U>::type; +}; + +template <typename T, typename U> +struct RebindPtr<T, U, void_t<typename T::template rebind<U>>> { + using type = typename T::template rebind<U>; +}; + +template <typename T, typename U, typename = void> +struct RebindAlloc { + using type = typename RebindFirstArg<T, U>::type; +}; + +template <typename T, typename U> +struct RebindAlloc<T, U, void_t<typename T::template rebind<U>::other>> { + using type = typename T::template rebind<U>::other; +}; + +} // namespace memory_internal + +// ----------------------------------------------------------------------------- +// Class Template: pointer_traits +// ----------------------------------------------------------------------------- +// +// An implementation of C++11's std::pointer_traits. +// +// Provided for portability on toolchains that have a working C++11 compiler, +// but the standard library is lacking in C++11 support. For example, some +// version of the Android NDK. +// + +template <typename Ptr> +struct pointer_traits { + using pointer = Ptr; + + // element_type: + // Ptr::element_type if present. Otherwise T if Ptr is a template + // instantiation Template<T, Args...> + using element_type = typename memory_internal::ElementType<Ptr>::type; + + // difference_type: + // Ptr::difference_type if present, otherwise std::ptrdiff_t + using difference_type = + memory_internal::ExtractOrT<memory_internal::GetDifferenceType, Ptr, + std::ptrdiff_t>; + + // rebind: + // Ptr::rebind<U> if exists, otherwise Template<U, Args...> if Ptr is a + // template instantiation Template<T, Args...> + template <typename U> + using rebind = typename memory_internal::RebindPtr<Ptr, U>::type; + + // pointer_to: + // Calls Ptr::pointer_to(r) + static pointer pointer_to(element_type& r) { // NOLINT(runtime/references) + return Ptr::pointer_to(r); + } +}; + +// Specialization for T*. +template <typename T> +struct pointer_traits<T*> { + using pointer = T*; + using element_type = T; + using difference_type = std::ptrdiff_t; + + template <typename U> + using rebind = U*; + + // pointer_to: + // Calls std::addressof(r) + static pointer pointer_to( + element_type& r) noexcept { // NOLINT(runtime/references) + return std::addressof(r); + } +}; + +// ----------------------------------------------------------------------------- +// Class Template: allocator_traits +// ----------------------------------------------------------------------------- +// +// A C++11 compatible implementation of C++17's std::allocator_traits. +// +template <typename Alloc> +struct allocator_traits { + using allocator_type = Alloc; + + // value_type: + // Alloc::value_type + using value_type = typename Alloc::value_type; + + // pointer: + // Alloc::pointer if present, otherwise value_type* + using pointer = memory_internal::ExtractOrT<memory_internal::GetPointer, + Alloc, value_type*>; + + // const_pointer: + // Alloc::const_pointer if present, otherwise + // absl::pointer_traits<pointer>::rebind<const value_type> + using const_pointer = + memory_internal::ExtractOrT<memory_internal::GetConstPointer, Alloc, + typename absl::pointer_traits<pointer>:: + template rebind<const value_type>>; + + // void_pointer: + // Alloc::void_pointer if present, otherwise + // absl::pointer_traits<pointer>::rebind<void> + using void_pointer = memory_internal::ExtractOrT< + memory_internal::GetVoidPointer, Alloc, + typename absl::pointer_traits<pointer>::template rebind<void>>; + + // const_void_pointer: + // Alloc::const_void_pointer if present, otherwise + // absl::pointer_traits<pointer>::rebind<const void> + using const_void_pointer = memory_internal::ExtractOrT< + memory_internal::GetConstVoidPointer, Alloc, + typename absl::pointer_traits<pointer>::template rebind<const void>>; + + // difference_type: + // Alloc::difference_type if present, otherwise + // absl::pointer_traits<pointer>::difference_type + using difference_type = memory_internal::ExtractOrT< + memory_internal::GetDifferenceType, Alloc, + typename absl::pointer_traits<pointer>::difference_type>; + + // size_type: + // Alloc::size_type if present, otherwise + // std::make_unsigned<difference_type>::type + using size_type = memory_internal::ExtractOrT< + memory_internal::GetSizeType, Alloc, + typename std::make_unsigned<difference_type>::type>; + + // propagate_on_container_copy_assignment: + // Alloc::propagate_on_container_copy_assignment if present, otherwise + // std::false_type + using propagate_on_container_copy_assignment = memory_internal::ExtractOrT< + memory_internal::GetPropagateOnContainerCopyAssignment, Alloc, + std::false_type>; + + // propagate_on_container_move_assignment: + // Alloc::propagate_on_container_move_assignment if present, otherwise + // std::false_type + using propagate_on_container_move_assignment = memory_internal::ExtractOrT< + memory_internal::GetPropagateOnContainerMoveAssignment, Alloc, + std::false_type>; + + // propagate_on_container_swap: + // Alloc::propagate_on_container_swap if present, otherwise std::false_type + using propagate_on_container_swap = + memory_internal::ExtractOrT<memory_internal::GetPropagateOnContainerSwap, + Alloc, std::false_type>; + + // is_always_equal: + // Alloc::is_always_equal if present, otherwise std::is_empty<Alloc>::type + using is_always_equal = + memory_internal::ExtractOrT<memory_internal::GetIsAlwaysEqual, Alloc, + typename std::is_empty<Alloc>::type>; + + // rebind_alloc: + // Alloc::rebind<T>::other if present, otherwise Alloc<T, Args> if this Alloc + // is Alloc<U, Args> + template <typename T> + using rebind_alloc = typename memory_internal::RebindAlloc<Alloc, T>::type; + + // rebind_traits: + // absl::allocator_traits<rebind_alloc<T>> + template <typename T> + using rebind_traits = absl::allocator_traits<rebind_alloc<T>>; + + // allocate(Alloc& a, size_type n): + // Calls a.allocate(n) + static pointer allocate(Alloc& a, // NOLINT(runtime/references) + size_type n) { + return a.allocate(n); + } + + // allocate(Alloc& a, size_type n, const_void_pointer hint): + // Calls a.allocate(n, hint) if possible. + // If not possible, calls a.allocate(n) + static pointer allocate(Alloc& a, size_type n, // NOLINT(runtime/references) + const_void_pointer hint) { + return allocate_impl(0, a, n, hint); + } + + // deallocate(Alloc& a, pointer p, size_type n): + // Calls a.deallocate(p, n) + static void deallocate(Alloc& a, pointer p, // NOLINT(runtime/references) + size_type n) { + a.deallocate(p, n); + } + + // construct(Alloc& a, T* p, Args&&... args): + // Calls a.construct(p, std::forward<Args>(args)...) if possible. + // If not possible, calls + // ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...) + template <typename T, typename... Args> + static void construct(Alloc& a, T* p, // NOLINT(runtime/references) + Args&&... args) { + construct_impl(0, a, p, std::forward<Args>(args)...); + } + + // destroy(Alloc& a, T* p): + // Calls a.destroy(p) if possible. If not possible, calls p->~T(). + template <typename T> + static void destroy(Alloc& a, T* p) { // NOLINT(runtime/references) + destroy_impl(0, a, p); + } + + // max_size(const Alloc& a): + // Returns a.max_size() if possible. If not possible, returns + // std::numeric_limits<size_type>::max() / sizeof(value_type) + static size_type max_size(const Alloc& a) { return max_size_impl(0, a); } + + // select_on_container_copy_construction(const Alloc& a): + // Returns a.select_on_container_copy_construction() if possible. + // If not possible, returns a. + static Alloc select_on_container_copy_construction(const Alloc& a) { + return select_on_container_copy_construction_impl(0, a); + } + + private: + template <typename A> + static auto allocate_impl(int, A& a, // NOLINT(runtime/references) + size_type n, const_void_pointer hint) + -> decltype(a.allocate(n, hint)) { + return a.allocate(n, hint); + } + static pointer allocate_impl(char, Alloc& a, // NOLINT(runtime/references) + size_type n, const_void_pointer) { + return a.allocate(n); + } + + template <typename A, typename... Args> + static auto construct_impl(int, A& a, // NOLINT(runtime/references) + Args&&... args) + -> decltype(a.construct(std::forward<Args>(args)...)) { + a.construct(std::forward<Args>(args)...); + } + + template <typename T, typename... Args> + static void construct_impl(char, Alloc&, T* p, Args&&... args) { + ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...); + } + + template <typename A, typename T> + static auto destroy_impl(int, A& a, // NOLINT(runtime/references) + T* p) -> decltype(a.destroy(p)) { + a.destroy(p); + } + template <typename T> + static void destroy_impl(char, Alloc&, T* p) { + p->~T(); + } + + template <typename A> + static auto max_size_impl(int, const A& a) -> decltype(a.max_size()) { + return a.max_size(); + } + static size_type max_size_impl(char, const Alloc&) { + return std::numeric_limits<size_type>::max() / sizeof(value_type); + } + + template <typename A> + static auto select_on_container_copy_construction_impl(int, const A& a) + -> decltype(a.select_on_container_copy_construction()) { + return a.select_on_container_copy_construction(); + } + static Alloc select_on_container_copy_construction_impl(char, + const Alloc& a) { + return a; + } +}; + +namespace memory_internal { + +// This template alias transforms Alloc::is_nothrow into a metafunction with +// Alloc as a parameter so it can be used with ExtractOrT<>. +template <typename Alloc> +using GetIsNothrow = typename Alloc::is_nothrow; + +} // namespace memory_internal + +// ABSL_ALLOCATOR_NOTHROW is a build time configuration macro for user to +// specify whether the default allocation function can throw or never throws. +// If the allocation function never throws, user should define it to a non-zero +// value (e.g. via `-DABSL_ALLOCATOR_NOTHROW`). +// If the allocation function can throw, user should leave it undefined or +// define it to zero. +// +// allocator_is_nothrow<Alloc> is a traits class that derives from +// Alloc::is_nothrow if present, otherwise std::false_type. It's specialized +// for Alloc = std::allocator<T> for any type T according to the state of +// ABSL_ALLOCATOR_NOTHROW. +// +// default_allocator_is_nothrow is a class that derives from std::true_type +// when the default allocator (global operator new) never throws, and +// std::false_type when it can throw. It is a convenience shorthand for writing +// allocator_is_nothrow<std::allocator<T>> (T can be any type). +// NOTE: allocator_is_nothrow<std::allocator<T>> is guaranteed to derive from +// the same type for all T, because users should specialize neither +// allocator_is_nothrow nor std::allocator. +template <typename Alloc> +struct allocator_is_nothrow + : memory_internal::ExtractOrT<memory_internal::GetIsNothrow, Alloc, + std::false_type> {}; + +#if ABSL_ALLOCATOR_NOTHROW +template <typename T> +struct allocator_is_nothrow<std::allocator<T>> : std::true_type {}; +struct default_allocator_is_nothrow : std::true_type {}; +#else +struct default_allocator_is_nothrow : std::false_type {}; +#endif + +} // namespace absl + +#endif // ABSL_MEMORY_MEMORY_H_ diff --git a/absl/memory/memory_test.cc b/absl/memory/memory_test.cc new file mode 100644 index 000000000000..8a5f5522a089 --- /dev/null +++ b/absl/memory/memory_test.cc @@ -0,0 +1,590 @@ +// Copyright 2017 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Tests for pointer utilities. + +#include "absl/memory/memory.h" + +#include <sys/types.h> +#include <cstddef> +#include <memory> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +using ::testing::ElementsAre; +using ::testing::Return; + +// This class creates observable behavior to verify that a destructor has +// been called, via the instance_count variable. +class DestructorVerifier { + public: + DestructorVerifier() { ++instance_count_; } + DestructorVerifier(const DestructorVerifier&) = delete; + DestructorVerifier& operator=(const DestructorVerifier&) = delete; + ~DestructorVerifier() { --instance_count_; } + + // The number of instances of this class currently active. + static int instance_count() { return instance_count_; } + + private: + // The number of instances of this class currently active. + static int instance_count_; +}; + +int DestructorVerifier::instance_count_ = 0; + +TEST(WrapUniqueTest, WrapUnique) { + // Test that the unique_ptr is constructed properly by verifying that the + // destructor for its payload gets called at the proper time. + { + auto dv = new DestructorVerifier; + EXPECT_EQ(1, DestructorVerifier::instance_count()); + std::unique_ptr<DestructorVerifier> ptr = absl::WrapUnique(dv); + EXPECT_EQ(1, DestructorVerifier::instance_count()); + } + EXPECT_EQ(0, DestructorVerifier::instance_count()); +} +TEST(MakeUniqueTest, Basic) { + std::unique_ptr<std::string> p = absl::make_unique<std::string>(); + EXPECT_EQ("", *p); + p = absl::make_unique<std::string>("hi"); + EXPECT_EQ("hi", *p); +} + +struct MoveOnly { + MoveOnly() = default; + explicit MoveOnly(int i1) : ip1{new int{i1}} {} + MoveOnly(int i1, int i2) : ip1{new int{i1}}, ip2{new int{i2}} {} + std::unique_ptr<int> ip1; + std::unique_ptr<int> ip2; +}; + +struct AcceptMoveOnly { + explicit AcceptMoveOnly(MoveOnly m) : m_(std::move(m)) {} + MoveOnly m_; +}; + +TEST(MakeUniqueTest, MoveOnlyTypeAndValue) { + using ExpectedType = std::unique_ptr<MoveOnly>; + { + auto p = absl::make_unique<MoveOnly>(); + static_assert(std::is_same<decltype(p), ExpectedType>::value, + "unexpected return type"); + EXPECT_TRUE(!p->ip1); + EXPECT_TRUE(!p->ip2); + } + { + auto p = absl::make_unique<MoveOnly>(1); + static_assert(std::is_same<decltype(p), ExpectedType>::value, + "unexpected return type"); + EXPECT_TRUE(p->ip1 && *p->ip1 == 1); + EXPECT_TRUE(!p->ip2); + } + { + auto p = absl::make_unique<MoveOnly>(1, 2); + static_assert(std::is_same<decltype(p), ExpectedType>::value, + "unexpected return type"); + EXPECT_TRUE(p->ip1 && *p->ip1 == 1); + EXPECT_TRUE(p->ip2 && *p->ip2 == 2); + } +} + +TEST(MakeUniqueTest, AcceptMoveOnly) { + auto p = absl::make_unique<AcceptMoveOnly>(MoveOnly()); + p = std::unique_ptr<AcceptMoveOnly>(new AcceptMoveOnly(MoveOnly())); +} + +struct ArrayWatch { + void* operator new[](size_t n) { + allocs().push_back(n); + return ::operator new[](n); + } + void operator delete[](void* p) { + return ::operator delete[](p); + } + static std::vector<size_t>& allocs() { + static auto& v = *new std::vector<size_t>; + return v; + } +}; + +TEST(Make_UniqueTest, Array) { + // Ensure state is clean before we start so that these tests + // are order-agnostic. + ArrayWatch::allocs().clear(); + + auto p = absl::make_unique<ArrayWatch[]>(5); + static_assert(std::is_same<decltype(p), + std::unique_ptr<ArrayWatch[]>>::value, + "unexpected return type"); + EXPECT_THAT(ArrayWatch::allocs(), ElementsAre(5 * sizeof(ArrayWatch))); +} + +#if 0 +// TODO(billydonahue): Make a proper NC test. +// These tests shouldn't compile. +TEST(MakeUniqueTestNC, AcceptMoveOnlyLvalue) { + auto m = MoveOnly(); + auto p = absl::make_unique<AcceptMoveOnly>(m); +} +TEST(MakeUniqueTestNC, KnownBoundArray) { + auto p = absl::make_unique<ArrayWatch[5]>(); +} +#endif + +TEST(RawPtrTest, RawPointer) { + int i = 5; + EXPECT_EQ(&i, absl::RawPtr(&i)); +} + +TEST(RawPtrTest, SmartPointer) { + int* o = new int(5); + std::unique_ptr<int> p(o); + EXPECT_EQ(o, absl::RawPtr(p)); +} + +class IntPointerNonConstDeref { + public: + explicit IntPointerNonConstDeref(int* p) : p_(p) {} + friend bool operator!=(const IntPointerNonConstDeref& a, std::nullptr_t) { + return a.p_ != nullptr; + } + int& operator*() { return *p_; } + + private: + std::unique_ptr<int> p_; +}; + +TEST(RawPtrTest, SmartPointerNonConstDereference) { + int* o = new int(5); + IntPointerNonConstDeref p(o); + EXPECT_EQ(o, absl::RawPtr(p)); +} + +TEST(RawPtrTest, NullValuedRawPointer) { + int* p = nullptr; + EXPECT_EQ(nullptr, absl::RawPtr(p)); +} + +TEST(RawPtrTest, NullValuedSmartPointer) { + std::unique_ptr<int> p; + EXPECT_EQ(nullptr, absl::RawPtr(p)); +} + +TEST(RawPtrTest, Nullptr) { + auto p = absl::RawPtr(nullptr); + EXPECT_TRUE((std::is_same<std::nullptr_t, decltype(p)>::value)); + EXPECT_EQ(nullptr, p); +} + +TEST(RawPtrTest, Null) { + auto p = absl::RawPtr(nullptr); + EXPECT_TRUE((std::is_same<std::nullptr_t, decltype(p)>::value)); + EXPECT_EQ(nullptr, p); +} + +TEST(RawPtrTest, Zero) { + auto p = absl::RawPtr(nullptr); + EXPECT_TRUE((std::is_same<std::nullptr_t, decltype(p)>::value)); + EXPECT_EQ(nullptr, p); +} + +TEST(ShareUniquePtrTest, Share) { + auto up = absl::make_unique<int>(); + int* rp = up.get(); + auto sp = absl::ShareUniquePtr(std::move(up)); + EXPECT_EQ(sp.get(), rp); +} + +TEST(ShareUniquePtrTest, ShareNull) { + struct NeverDie { + using pointer = void*; + void operator()(pointer) { + ASSERT_TRUE(false) << "Deleter should not have been called."; + } + }; + + std::unique_ptr<void, NeverDie> up; + auto sp = absl::ShareUniquePtr(std::move(up)); +} + +TEST(WeakenPtrTest, Weak) { + auto sp = std::make_shared<int>(); + auto wp = absl::WeakenPtr(sp); + EXPECT_EQ(sp.get(), wp.lock().get()); + sp.reset(); + EXPECT_TRUE(wp.expired()); +} + +// Should not compile. +/* +TEST(RawPtrTest, NotAPointer) { + absl::RawPtr(1.5); +} +*/ + +template <typename T> +struct SmartPointer { + using difference_type = char; +}; + +struct PointerWith { + using element_type = int32_t; + using difference_type = int16_t; + template <typename U> + using rebind = SmartPointer<U>; + + static PointerWith pointer_to( + element_type& r) { // NOLINT(runtime/references) + return PointerWith{&r}; + } + + element_type* ptr; +}; + +template <typename... Args> +struct PointerWithout {}; + +TEST(PointerTraits, Types) { + using TraitsWith = absl::pointer_traits<PointerWith>; + EXPECT_TRUE((std::is_same<TraitsWith::pointer, PointerWith>::value)); + EXPECT_TRUE((std::is_same<TraitsWith::element_type, int32_t>::value)); + EXPECT_TRUE((std::is_same<TraitsWith::difference_type, int16_t>::value)); + EXPECT_TRUE(( + std::is_same<TraitsWith::rebind<int64_t>, SmartPointer<int64_t>>::value)); + + using TraitsWithout = absl::pointer_traits<PointerWithout<double, int>>; + EXPECT_TRUE((std::is_same<TraitsWithout::pointer, + PointerWithout<double, int>>::value)); + EXPECT_TRUE((std::is_same<TraitsWithout::element_type, double>::value)); + EXPECT_TRUE( + (std::is_same<TraitsWithout ::difference_type, std::ptrdiff_t>::value)); + EXPECT_TRUE((std::is_same<TraitsWithout::rebind<int64_t>, + PointerWithout<int64_t, int>>::value)); + + using TraitsRawPtr = absl::pointer_traits<char*>; + EXPECT_TRUE((std::is_same<TraitsRawPtr::pointer, char*>::value)); + EXPECT_TRUE((std::is_same<TraitsRawPtr::element_type, char>::value)); + EXPECT_TRUE( + (std::is_same<TraitsRawPtr::difference_type, std::ptrdiff_t>::value)); + EXPECT_TRUE((std::is_same<TraitsRawPtr::rebind<int64_t>, int64_t*>::value)); +} + +TEST(PointerTraits, Functions) { + int i; + EXPECT_EQ(&i, absl::pointer_traits<PointerWith>::pointer_to(i).ptr); + EXPECT_EQ(&i, absl::pointer_traits<int*>::pointer_to(i)); +} + +TEST(AllocatorTraits, Typedefs) { + struct A { + struct value_type {}; + }; + EXPECT_TRUE(( + std::is_same<A, + typename absl::allocator_traits<A>::allocator_type>::value)); + EXPECT_TRUE( + (std::is_same<A::value_type, + typename absl::allocator_traits<A>::value_type>::value)); + + struct X {}; + struct HasPointer { + using value_type = X; + using pointer = SmartPointer<X>; + }; + EXPECT_TRUE((std::is_same<SmartPointer<X>, typename absl::allocator_traits< + HasPointer>::pointer>::value)); + EXPECT_TRUE( + (std::is_same<A::value_type*, + typename absl::allocator_traits<A>::pointer>::value)); + + EXPECT_TRUE( + (std::is_same< + SmartPointer<const X>, + typename absl::allocator_traits<HasPointer>::const_pointer>::value)); + EXPECT_TRUE( + (std::is_same<const A::value_type*, + typename absl::allocator_traits<A>::const_pointer>::value)); + + struct HasVoidPointer { + using value_type = X; + struct void_pointer {}; + }; + + EXPECT_TRUE((std::is_same<HasVoidPointer::void_pointer, + typename absl::allocator_traits< + HasVoidPointer>::void_pointer>::value)); + EXPECT_TRUE( + (std::is_same<SmartPointer<void>, typename absl::allocator_traits< + HasPointer>::void_pointer>::value)); + + struct HasConstVoidPointer { + using value_type = X; + struct const_void_pointer {}; + }; + + EXPECT_TRUE( + (std::is_same<HasConstVoidPointer::const_void_pointer, + typename absl::allocator_traits< + HasConstVoidPointer>::const_void_pointer>::value)); + EXPECT_TRUE((std::is_same<SmartPointer<const void>, + typename absl::allocator_traits< + HasPointer>::const_void_pointer>::value)); + + struct HasDifferenceType { + using value_type = X; + using difference_type = int; + }; + EXPECT_TRUE( + (std::is_same<int, typename absl::allocator_traits< + HasDifferenceType>::difference_type>::value)); + EXPECT_TRUE((std::is_same<char, typename absl::allocator_traits< + HasPointer>::difference_type>::value)); + + struct HasSizeType { + using value_type = X; + using size_type = unsigned int; + }; + EXPECT_TRUE((std::is_same<unsigned int, typename absl::allocator_traits< + HasSizeType>::size_type>::value)); + EXPECT_TRUE((std::is_same<unsigned char, typename absl::allocator_traits< + HasPointer>::size_type>::value)); + + struct HasPropagateOnCopy { + using value_type = X; + struct propagate_on_container_copy_assignment {}; + }; + + EXPECT_TRUE( + (std::is_same<HasPropagateOnCopy::propagate_on_container_copy_assignment, + typename absl::allocator_traits<HasPropagateOnCopy>:: + propagate_on_container_copy_assignment>::value)); + EXPECT_TRUE( + (std::is_same<std::false_type, + typename absl::allocator_traits< + A>::propagate_on_container_copy_assignment>::value)); + + struct HasPropagateOnMove { + using value_type = X; + struct propagate_on_container_move_assignment {}; + }; + + EXPECT_TRUE( + (std::is_same<HasPropagateOnMove::propagate_on_container_move_assignment, + typename absl::allocator_traits<HasPropagateOnMove>:: + propagate_on_container_move_assignment>::value)); + EXPECT_TRUE( + (std::is_same<std::false_type, + typename absl::allocator_traits< + A>::propagate_on_container_move_assignment>::value)); + + struct HasPropagateOnSwap { + using value_type = X; + struct propagate_on_container_swap {}; + }; + + EXPECT_TRUE( + (std::is_same<HasPropagateOnSwap::propagate_on_container_swap, + typename absl::allocator_traits<HasPropagateOnSwap>:: + propagate_on_container_swap>::value)); + EXPECT_TRUE( + (std::is_same<std::false_type, typename absl::allocator_traits<A>:: + propagate_on_container_swap>::value)); + + struct HasIsAlwaysEqual { + using value_type = X; + struct is_always_equal {}; + }; + + EXPECT_TRUE((std::is_same<HasIsAlwaysEqual::is_always_equal, + typename absl::allocator_traits< + HasIsAlwaysEqual>::is_always_equal>::value)); + EXPECT_TRUE((std::is_same<std::true_type, typename absl::allocator_traits< + A>::is_always_equal>::value)); + struct NonEmpty { + using value_type = X; + int i; + }; + EXPECT_TRUE( + (std::is_same<std::false_type, + absl::allocator_traits<NonEmpty>::is_always_equal>::value)); +} + +template <typename T> +struct Rebound {}; + +struct AllocWithRebind { + using value_type = int; + template <typename T> + struct rebind { + using other = Rebound<T>; + }; +}; + +template <typename T, typename U> +struct AllocWithoutRebind { + using value_type = int; +}; + +TEST(AllocatorTraits, Rebind) { + EXPECT_TRUE( + (std::is_same<Rebound<int>, + typename absl::allocator_traits< + AllocWithRebind>::template rebind_alloc<int>>::value)); + EXPECT_TRUE( + (std::is_same<absl::allocator_traits<Rebound<int>>, + typename absl::allocator_traits< + AllocWithRebind>::template rebind_traits<int>>::value)); + + EXPECT_TRUE( + (std::is_same<AllocWithoutRebind<double, char>, + typename absl::allocator_traits<AllocWithoutRebind< + int, char>>::template rebind_alloc<double>>::value)); + EXPECT_TRUE( + (std::is_same<absl::allocator_traits<AllocWithoutRebind<double, char>>, + typename absl::allocator_traits<AllocWithoutRebind< + int, char>>::template rebind_traits<double>>::value)); +} + +struct TestValue { + TestValue() {} + explicit TestValue(int* trace) : trace(trace) { ++*trace; } + ~TestValue() { + if (trace) --*trace; + } + int* trace = nullptr; +}; + +struct MinimalMockAllocator { + MinimalMockAllocator() : value(0) {} + explicit MinimalMockAllocator(int value) : value(value) {} + MinimalMockAllocator(const MinimalMockAllocator& other) + : value(other.value) {} + using value_type = TestValue; + MOCK_METHOD1(allocate, value_type*(size_t)); + MOCK_METHOD2(deallocate, void(value_type*, size_t)); + + int value; +}; + +TEST(AllocatorTraits, FunctionsMinimal) { + int trace = 0; + int hint; + TestValue x(&trace); + MinimalMockAllocator mock; + using Traits = absl::allocator_traits<MinimalMockAllocator>; + EXPECT_CALL(mock, allocate(7)).WillRepeatedly(Return(&x)); + EXPECT_CALL(mock, deallocate(&x, 7)); + + EXPECT_EQ(&x, Traits::allocate(mock, 7)); + Traits::allocate(mock, 7, static_cast<const void*>(&hint)); + EXPECT_EQ(&x, Traits::allocate(mock, 7, static_cast<const void*>(&hint))); + Traits::deallocate(mock, &x, 7); + + EXPECT_EQ(1, trace); + Traits::construct(mock, &x, &trace); + EXPECT_EQ(2, trace); + Traits::destroy(mock, &x); + EXPECT_EQ(1, trace); + + EXPECT_EQ(std::numeric_limits<size_t>::max() / sizeof(TestValue), + Traits::max_size(mock)); + + EXPECT_EQ(0, mock.value); + EXPECT_EQ(0, Traits::select_on_container_copy_construction(mock).value); +} + +struct FullMockAllocator { + FullMockAllocator() : value(0) {} + explicit FullMockAllocator(int value) : value(value) {} + FullMockAllocator(const FullMockAllocator& other) : value(other.value) {} + using value_type = TestValue; + MOCK_METHOD1(allocate, value_type*(size_t)); + MOCK_METHOD2(allocate, value_type*(size_t, const void*)); + MOCK_METHOD2(construct, void(value_type*, int*)); + MOCK_METHOD1(destroy, void(value_type*)); + MOCK_CONST_METHOD0(max_size, size_t()); + MOCK_CONST_METHOD0(select_on_container_copy_construction, + FullMockAllocator()); + + int value; +}; + +TEST(AllocatorTraits, FunctionsFull) { + int trace = 0; + int hint; + TestValue x(&trace), y; + FullMockAllocator mock; + using Traits = absl::allocator_traits<FullMockAllocator>; + EXPECT_CALL(mock, allocate(7)).WillRepeatedly(Return(&x)); + EXPECT_CALL(mock, allocate(13, &hint)).WillRepeatedly(Return(&y)); + EXPECT_CALL(mock, construct(&x, &trace)); + EXPECT_CALL(mock, destroy(&x)); + EXPECT_CALL(mock, max_size()).WillRepeatedly(Return(17)); + EXPECT_CALL(mock, select_on_container_copy_construction()) + .WillRepeatedly(Return(FullMockAllocator(23))); + + EXPECT_EQ(&x, Traits::allocate(mock, 7)); + EXPECT_EQ(&y, Traits::allocate(mock, 13, static_cast<const void*>(&hint))); + + EXPECT_EQ(1, trace); + Traits::construct(mock, &x, &trace); + EXPECT_EQ(1, trace); + Traits::destroy(mock, &x); + EXPECT_EQ(1, trace); + + EXPECT_EQ(17, Traits::max_size(mock)); + + EXPECT_EQ(0, mock.value); + EXPECT_EQ(23, Traits::select_on_container_copy_construction(mock).value); +} + +TEST(AllocatorNoThrowTest, DefaultAllocator) { +#if ABSL_ALLOCATOR_NOTHROW + EXPECT_TRUE(absl::default_allocator_is_nothrow::value); +#else + EXPECT_FALSE(absl::default_allocator_is_nothrow::value); +#endif +} + +TEST(AllocatorNoThrowTest, StdAllocator) { +#if ABSL_ALLOCATOR_NOTHROW + EXPECT_TRUE(absl::allocator_is_nothrow<std::allocator<int>>::value); +#else + EXPECT_FALSE(absl::allocator_is_nothrow<std::allocator<int>>::value); +#endif +} + +TEST(AllocatorNoThrowTest, CustomAllocator) { + struct NoThrowAllocator { + using is_nothrow = std::true_type; + }; + struct CanThrowAllocator { + using is_nothrow = std::false_type; + }; + struct UnspecifiedAllocator { + }; + EXPECT_TRUE(absl::allocator_is_nothrow<NoThrowAllocator>::value); + EXPECT_FALSE(absl::allocator_is_nothrow<CanThrowAllocator>::value); + EXPECT_FALSE(absl::allocator_is_nothrow<UnspecifiedAllocator>::value); +} + +} // namespace |