blob: c93586837c78108851f808cd3a93dc50be7cfd08 [file] [log] [blame]
Alex Vakulenko9205c772014-08-22 15:05:35 -07001// Copyright 2014 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Internal implementation of chromeos::Any class.
6
7#ifndef LIBCHROMEOS_CHROMEOS_ANY_INTERNAL_IMPL_H_
8#define LIBCHROMEOS_CHROMEOS_ANY_INTERNAL_IMPL_H_
9
10#include <type_traits>
11#include <typeinfo>
12#include <utility>
13
14#include <base/logging.h>
Alex Vakulenkob3813652014-08-27 08:04:22 -070015#include <chromeos/dbus/data_serialization.h>
Alex Vakulenko9205c772014-08-22 15:05:35 -070016
17namespace chromeos {
18
19namespace internal_details {
20
21// An extension to std::is_convertible to allow conversion from an enum to
22// an integral type which std::is_convertible does not indicate as supported.
23template <typename From, typename To>
24struct IsConvertible : public std::integral_constant<bool,
25 std::is_convertible<From, To>::value ||
26 (std::is_enum<From>::value && std::is_integral<To>::value)> {
27};
28// TryConvert is a helper function that does a safe compile-time conditional
29// type cast between data types that may not be always convertible.
30// From and To are the source and destination types.
31// The function returns true if conversion was possible/successful.
32template <typename From, typename To>
33inline typename std::enable_if<IsConvertible<From, To>::value, bool>::type
34TryConvert(const From& in, To* out) {
35 *out = static_cast<To>(in);
36 return true;
37}
38template <typename From, typename To>
39inline typename std::enable_if<!IsConvertible<From, To>::value, bool>::type
40TryConvert(const From& in, To* out) {
41 return false;
42}
43
44class Buffer; // Forward declaration of data buffer container.
45
46// Abstract base class for contained variant data.
47struct Data {
48 virtual ~Data() {}
49 // Returns the type information for the contained data.
50 virtual const std::type_info& GetType() const = 0;
51 // Copies the contained data to the output |buffer|.
52 virtual void CopyTo(Buffer* buffer) const = 0;
53 // Moves the contained data to the output |buffer|.
54 virtual void MoveTo(Buffer* buffer) = 0;
55 // Checks if the contained data is an integer type (not necessarily an 'int').
56 virtual bool IsConvertibleToInteger() const = 0;
57 // Gets the contained integral value as an integer.
58 virtual intmax_t GetAsInteger() const = 0;
Alex Vakulenko7c294a22014-08-23 19:31:27 -070059 // Writes the contained value to the D-Bus message buffer.
60 virtual bool AppendToDBusMessage(dbus::MessageWriter* writer) const = 0;
Alex Vakulenko9205c772014-08-22 15:05:35 -070061};
62
63// Concrete implementation of variant data of type T.
64template<typename T>
65struct TypedData : public Data {
66 explicit TypedData(const T& value) : value_(value) {}
67 // NOLINTNEXTLINE(build/c++11)
68 explicit TypedData(T&& value) : value_(std::move(value)) {}
69
70 const std::type_info& GetType() const override { return typeid(T); }
71 void CopyTo(Buffer* buffer) const override;
72 void MoveTo(Buffer* buffer) override;
73 bool IsConvertibleToInteger() const override {
74 return std::is_integral<T>::value || std::is_enum<T>::value;
75 }
76 intmax_t GetAsInteger() const override {
77 intmax_t int_val = 0;
78 bool converted = TryConvert(value_, &int_val);
79 CHECK(converted) << "Unable to convert value of type " << typeid(T).name()
80 << " to integer";
81 return int_val;
82 }
Alex Vakulenko7c294a22014-08-23 19:31:27 -070083 bool AppendToDBusMessage(dbus::MessageWriter* writer) const override {
84 return chromeos::dbus_utils::AppendValueToWriterAsVariant(writer, value_);
85 }
Alex Vakulenko9205c772014-08-22 15:05:35 -070086 // Special methods to copy/move data of the same type
87 // without reallocating the buffer.
88 void FastAssign(const T& source) { value_ = source; }
89 // NOLINTNEXTLINE(build/c++11)
90 void FastAssign(T&& source) { value_ = std::move(source); }
91
92 T value_;
93};
94
95// Buffer class that stores the contained variant data.
96// To improve performance and reduce memory fragmentation, small variants
97// are stored in pre-allocated memory buffers that are part of the Any class.
98// If the memory requirements are larger than the set limit or the type is
99// non-trivially copyable, then the contained class is allocated in a separate
100// memory block and the pointer to that memory is contained within this memory
101// buffer class.
102class Buffer {
103 public:
104 enum StorageType { kExternal, kContained };
105 Buffer() : external_ptr_(nullptr), storage_(kExternal) {}
106 ~Buffer() {
107 Clear();
108 }
109
110 Buffer(const Buffer& rhs) : Buffer() {
111 rhs.CopyTo(this);
112 }
113 // NOLINTNEXTLINE(build/c++11)
114 Buffer(Buffer&& rhs) : Buffer() {
115 rhs.MoveTo(this);
116 }
117 Buffer& operator=(const Buffer& rhs) {
118 rhs.CopyTo(this);
119 return *this;
120 }
121 // NOLINTNEXTLINE(build/c++11)
122 Buffer& operator=(Buffer&& rhs) {
123 rhs.MoveTo(this);
124 return *this;
125 }
126
127 // Returns the underlying pointer to contained data. Uses either the pointer
128 // or the raw data depending on |storage_| type.
129 inline Data* GetDataPtr() {
130 return (storage_ == kExternal) ?
131 external_ptr_ : reinterpret_cast<Data*>(contained_buffer_);
132 }
133 inline const Data* GetDataPtr() const {
134 return (storage_ == kExternal) ?
135 external_ptr_ : reinterpret_cast<const Data*>(contained_buffer_);
136 }
137
138 // Destroys the contained object (and frees memory if needed).
139 void Clear() {
140 Data* data = GetDataPtr();
141 if (storage_ == kExternal) {
142 delete data;
143 } else {
144 // Call the destructor manually, since the object was constructed inline
145 // in the pre-allocated buffer. We still need to call the destructor
146 // to free any associated resources, but we can't call delete |data| here.
147 data->~Data();
148 }
149 external_ptr_ = nullptr;
150 storage_ = kExternal;
151 }
152
153 // Stores a value of type T.
154 template<typename T>
155 void Assign(T&& value) { // NOLINT(build/c++11)
156 using Type = typename std::decay<T>::type;
157 using DataType = TypedData<Type>;
158 Data* ptr = GetDataPtr();
159 if (ptr && ptr->GetType() == typeid(Type)) {
160 // We assign the data to the variant container, which already
161 // has the data of the same type. Do fast copy/move with no memory
162 // reallocation.
163 DataType* typed_ptr = static_cast<DataType*>(ptr);
164 // NOLINTNEXTLINE(build/c++11)
165 typed_ptr->FastAssign(std::forward<T>(value));
166 } else {
167 Clear();
168 // TODO(avakulenko): [see crbug.com/379833]
169 // Unfortunately, GCC doesn't support std::is_trivially_copyable<T> yet,
170 // so using std::is_trivial instead, which is a bit more restrictive.
171 // Once GCC has support for is_trivially_copyable, update the following.
172 if (!std::is_trivial<Type>::value ||
173 sizeof(DataType) > sizeof(contained_buffer_)) {
174 // If it is too big or not trivially copyable, allocate it separately.
175 // NOLINTNEXTLINE(build/c++11)
176 external_ptr_ = new DataType(std::forward<T>(value));
177 storage_ = kExternal;
178 } else {
179 // Otherwise just use the pre-allocated buffer.
180 DataType* address = reinterpret_cast<DataType*>(contained_buffer_);
181 // Make sure we still call the copy/move constructor.
182 // Call the constructor manually by using placement 'new'.
183 // NOLINTNEXTLINE(build/c++11)
184 new (address) DataType(std::forward<T>(value));
185 storage_ = kContained;
186 }
187 }
188 }
189
190 // Helper methods to retrieve a reference to contained data.
191 // These assume that type checking has already been performed by Any
192 // so the type cast is valid and will succeed.
193 template<typename T>
194 const T& GetData() const {
195 using DataType = internal_details::TypedData<typename std::decay<T>::type>;
196 return static_cast<const DataType*>(GetDataPtr())->value_;
197 }
198 template<typename T>
199 T& GetData() {
200 using DataType = internal_details::TypedData<typename std::decay<T>::type>;
201 return static_cast<DataType*>(GetDataPtr())->value_;
202 }
203
204 // Returns true if the buffer has no contained data.
205 bool IsEmpty() const {
206 return (storage_ == kExternal && external_ptr_ == nullptr);
207 }
208
209 // Copies the data from the current buffer into the |destination|.
210 void CopyTo(Buffer* destination) const {
211 if (IsEmpty()) {
212 destination->Clear();
213 } else {
214 GetDataPtr()->CopyTo(destination);
215 }
216 }
217
218 // Moves the data from the current buffer into the |destination|.
219 void MoveTo(Buffer* destination) {
220 if (IsEmpty()) {
221 destination->Clear();
222 } else {
223 if (storage_ == kExternal) {
224 destination->Clear();
225 destination->storage_ = kExternal;
226 destination->external_ptr_ = external_ptr_;
227 external_ptr_ = nullptr;
228 } else {
229 GetDataPtr()->MoveTo(destination);
230 }
231 }
232 }
233
234 union {
235 // |external_ptr_| is a pointer to a larger object allocated in
236 // a separate memory block.
237 Data* external_ptr_;
238 // |contained_buffer_| is a pre-allocated buffer for smaller/simple objects.
239 // Pre-allocate enough memory to store objects as big as "double".
240 unsigned char contained_buffer_[sizeof(TypedData<double>)];
241 };
242 // Depending on a value of |storage_|, either |external_ptr_| or
243 // |contained_buffer_| above is used to get a pointer to memory containing
244 // the variant data.
245 StorageType storage_; // Declare after the union to eliminate member padding.
246};
247
248template<typename T>
249void TypedData<T>::CopyTo(Buffer* buffer) const { buffer->Assign(value_); }
250template<typename T>
251void TypedData<T>::MoveTo(Buffer* buffer) { buffer->Assign(std::move(value_)); }
252
253} // namespace internal_details
254
255} // namespace chromeos
256
257#endif // LIBCHROMEOS_CHROMEOS_ANY_INTERNAL_IMPL_H_
258