Improve heap allocation.
diff --git a/c++/src/capnproto/compiler/capnpc-capnp.c++ b/c++/src/capnproto/compiler/capnpc-capnp.c++
index 8002e2a..9a82517 100644
--- a/c++/src/capnproto/compiler/capnpc-capnp.c++
+++ b/c++/src/capnproto/compiler/capnpc-capnp.c++
@@ -95,7 +95,7 @@
}
TextBlob::TextBlob(kj::Array<TextBlob>&& params) {
- branches = kj::newArray<Branch>(params.size());
+ branches = kj::heapArray<Branch>(params.size());
for (size_t i = 0; i < params.size(); i++) {
branches[i].pos = nullptr;
branches[i].content = kj::mv(params[i]);
@@ -113,8 +113,8 @@
}
void TextBlob::allocate(size_t textSize, size_t branchCount) {
- text = kj::newArray<char>(textSize);
- branches = kj::newArray<Branch>(branchCount);
+ text = kj::heapArray<char>(textSize);
+ branches = kj::heapArray<Branch>(branchCount);
}
template <typename First, typename... Rest>
@@ -160,7 +160,7 @@
template <typename List, typename Func>
TextBlob forText(List&& list, Func&& func) {
- kj::Array<TextBlob> items = kj::newArray<TextBlob>(list.size());
+ kj::Array<TextBlob> items = kj::heapArray<TextBlob>(list.size());
for (size_t i = 0; i < list.size(); i++) {
items[i] = func(list[i]);
}
diff --git a/c++/src/capnproto/schema-loader.c++ b/c++/src/capnproto/schema-loader.c++
index 994fb55..34bd9dc 100644
--- a/c++/src/capnproto/schema-loader.c++
+++ b/c++/src/capnproto/schema-loader.c++
@@ -1096,7 +1096,7 @@
}
kj::Array<Schema> SchemaLoader::Impl::getAllLoaded() const {
- kj::Array<Schema> result = kj::newArray<Schema>(schemas.size());
+ kj::Array<Schema> result = kj::heapArray<Schema>(schemas.size());
size_t i = 0;
for (auto& schema: schemas) {
result[i++] = Schema(schema.second);
diff --git a/c++/src/capnproto/schema-loader.h b/c++/src/capnproto/schema-loader.h
index c5fa31b..ebaa32b 100644
--- a/c++/src/capnproto/schema-loader.h
+++ b/c++/src/capnproto/schema-loader.h
@@ -25,6 +25,7 @@
#define CAPNPROTO_SCHEMA_LOADER_H_
#include "schema.h"
+#include <kj/memory.h>
namespace capnproto {
diff --git a/c++/src/capnproto/serialize-snappy.c++ b/c++/src/capnproto/serialize-snappy.c++
index fd1d0bf..75473fb 100644
--- a/c++/src/capnproto/serialize-snappy.c++
+++ b/c++/src/capnproto/serialize-snappy.c++
@@ -61,7 +61,7 @@
SnappyInputStream::SnappyInputStream(BufferedInputStream& inner, kj::ArrayPtr<byte> buffer)
: inner(inner) {
if (buffer.size() < SNAPPY_BUFFER_SIZE) {
- ownedBuffer = kj::newArray<byte>(SNAPPY_BUFFER_SIZE);
+ ownedBuffer = kj::heapArray<byte>(SNAPPY_BUFFER_SIZE);
buffer = ownedBuffer;
}
this->buffer = buffer;
@@ -125,14 +125,14 @@
"snappy::MaxCompressedLength() changed?");
if (buffer.size() < SNAPPY_BUFFER_SIZE) {
- ownedBuffer = kj::newArray<byte>(SNAPPY_BUFFER_SIZE);
+ ownedBuffer = kj::heapArray<byte>(SNAPPY_BUFFER_SIZE);
buffer = ownedBuffer;
}
this->buffer = buffer;
bufferPos = buffer.begin();
if (compressedBuffer.size() < SNAPPY_COMPRESSED_BUFFER_SIZE) {
- ownedCompressedBuffer = kj::newArray<byte>(SNAPPY_COMPRESSED_BUFFER_SIZE);
+ ownedCompressedBuffer = kj::heapArray<byte>(SNAPPY_COMPRESSED_BUFFER_SIZE);
compressedBuffer = ownedCompressedBuffer;
}
this->compressedBuffer = compressedBuffer;
diff --git a/c++/src/capnproto/serialize-test.c++ b/c++/src/capnproto/serialize-test.c++
index 6d61525..3c67284 100644
--- a/c++/src/capnproto/serialize-test.c++
+++ b/c++/src/capnproto/serialize-test.c++
@@ -290,7 +290,7 @@
}
TEST(Serialize, RejectTooManySegments) {
- kj::Array<word> data = kj::newArray<word>(8192);
+ kj::Array<word> data = kj::heapArray<word>(8192);
WireValue<uint32_t>* table = reinterpret_cast<WireValue<uint32_t>*>(data.begin());
table[0].set(1024);
for (uint i = 0; i < 1024; i++) {
diff --git a/c++/src/capnproto/serialize.c++ b/c++/src/capnproto/serialize.c++
index 86f81f8..bb6fd5b 100644
--- a/c++/src/capnproto/serialize.c++
+++ b/c++/src/capnproto/serialize.c++
@@ -61,7 +61,7 @@
offset += segmentSize;
if (segmentCount > 1) {
- moreSegments = kj::newArray<kj::ArrayPtr<const word>>(segmentCount - 1);
+ moreSegments = kj::heapArray<kj::ArrayPtr<const word>>(segmentCount - 1);
for (uint i = 1; i < segmentCount; i++) {
uint segmentSize = table[i + 1].get();
@@ -96,7 +96,7 @@
totalSize += segment.size();
}
- kj::Array<word> result = kj::newArray<word>(totalSize);
+ kj::Array<word> result = kj::heapArray<word>(totalSize);
internal::WireValue<uint32_t>* table =
reinterpret_cast<internal::WireValue<uint32_t>*>(result.begin());
@@ -170,14 +170,14 @@
if (scratchSpace.size() < totalWords) {
// TODO(perf): Consider allocating each segment as a separate chunk to reduce memory
// fragmentation.
- ownedSpace = kj::newArray<word>(totalWords);
+ ownedSpace = kj::heapArray<word>(totalWords);
scratchSpace = ownedSpace;
}
segment0 = scratchSpace.slice(0, segment0Size);
if (segmentCount > 1) {
- moreSegments = kj::newArray<kj::ArrayPtr<const word>>(segmentCount - 1);
+ moreSegments = kj::heapArray<kj::ArrayPtr<const word>>(segmentCount - 1);
size_t offset = segment0Size;
for (uint i = 0; i < segmentCount - 1; i++) {
diff --git a/c++/src/kj/array-test.c++ b/c++/src/kj/array-test.c++
new file mode 100644
index 0000000..0730c3c
--- /dev/null
+++ b/c++/src/kj/array-test.c++
@@ -0,0 +1,287 @@
+// Copyright (c) 2013, Kenton Varda <[email protected]>
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "array.h"
+#include <gtest/gtest.h>
+#include "logging.h"
+#include <string>
+#include <list>
+
+namespace kj {
+namespace {
+
+struct TestObject {
+ TestObject() {
+ index = count;
+ CHECK(index != throwAt);
+ ++count;
+ }
+ TestObject(const TestObject& other) {
+ CHECK(other.index != throwAt);
+ index = -1;
+ copiedCount++;
+ }
+ ~TestObject() noexcept(false) {
+ if (index == -1) {
+ --copiedCount;
+ } else {
+ --count;
+ EXPECT_EQ(index, count);
+ CHECK(count != throwAt);
+ }
+ }
+
+ int index;
+
+ static int count;
+ static int copiedCount;
+ static int throwAt;
+};
+
+int TestObject::count = 0;
+int TestObject::copiedCount = 0;
+int TestObject::throwAt = -1;
+
+struct TestNoexceptObject {
+ TestNoexceptObject() noexcept {
+ index = count;
+ ++count;
+ }
+ TestNoexceptObject(const TestNoexceptObject& other) noexcept {
+ index = -1;
+ copiedCount++;
+ }
+ ~TestNoexceptObject() noexcept {
+ if (index == -1) {
+ --copiedCount;
+ } else {
+ --count;
+ EXPECT_EQ(index, count);
+ }
+ }
+
+ int index;
+
+ static int count;
+ static int copiedCount;
+};
+
+int TestNoexceptObject::count = 0;
+int TestNoexceptObject::copiedCount = 0;
+
+TEST(Array, TrivialConstructor) {
+ char* ptr;
+ {
+ Array<char> chars = heapArray<char>(32);
+ ptr = chars.begin();
+ chars[0] = 12;
+ chars[1] = 34;
+ }
+
+ {
+ Array<char> chars = heapArray<char>(32);
+
+ // Somewhat hacky: We can't guarantee that the new array is allocated in the same place, but
+ // any reasonable allocator is highly likely to do so. If it does, then we expect that the
+ // memory has not been initialized.
+ if (chars.begin() == ptr) {
+ EXPECT_NE(chars[0], 0);
+ EXPECT_NE(chars[1], 0);
+ }
+ }
+}
+
+TEST(Array, ComplexConstructor) {
+ TestObject::count = 0;
+ TestObject::throwAt = -1;
+
+ {
+ Array<TestObject> array = heapArray<TestObject>(32);
+ EXPECT_EQ(32, TestObject::count);
+ }
+ EXPECT_EQ(0, TestObject::count);
+}
+
+TEST(Array, ThrowingConstructor) {
+ TestObject::count = 0;
+ TestObject::throwAt = 16;
+
+ // If a constructor throws, the previous elements should still be destroyed.
+ EXPECT_ANY_THROW(heapArray<TestObject>(32));
+ EXPECT_EQ(0, TestObject::count);
+}
+
+TEST(Array, ThrowingDestructor) {
+ TestObject::count = 0;
+ TestObject::throwAt = -1;
+
+ Array<TestObject> array = heapArray<TestObject>(32);
+ EXPECT_EQ(32, TestObject::count);
+
+ // If a destructor throws, all elements should still be destroyed.
+ TestObject::throwAt = 16;
+ EXPECT_ANY_THROW(array = nullptr);
+ EXPECT_EQ(0, TestObject::count);
+}
+
+TEST(Array, AraryBuilder) {
+ TestObject::count = 0;
+ TestObject::throwAt = -1;
+
+ Array<TestObject> array;
+
+ {
+ ArrayBuilder<TestObject> builder = heapArrayBuilder<TestObject>(32);
+
+ for (uint i = 0; i < 32; i++) {
+ EXPECT_EQ(i, TestObject::count);
+ builder.add();
+ }
+
+ EXPECT_EQ(32, TestObject::count);
+ array = builder.finish();
+ EXPECT_EQ(32, TestObject::count);
+ }
+
+ EXPECT_EQ(32, TestObject::count);
+ array = nullptr;
+ EXPECT_EQ(0, TestObject::count);
+}
+
+TEST(Array, AraryBuilderAddAll) {
+ {
+ // Trivial case.
+ char text[] = "foo";
+ ArrayBuilder<char> builder = heapArrayBuilder<char>(5);
+ builder.add('<');
+ builder.addAll(text, text + 3);
+ builder.add('>');
+ auto array = builder.finish();
+ EXPECT_EQ("<foo>", std::string(array.begin(), array.end()));
+ }
+
+ {
+ // Trivial case, const.
+ const char* text = "foo";
+ ArrayBuilder<char> builder = heapArrayBuilder<char>(5);
+ builder.add('<');
+ builder.addAll(text, text + 3);
+ builder.add('>');
+ auto array = builder.finish();
+ EXPECT_EQ("<foo>", std::string(array.begin(), array.end()));
+ }
+
+ {
+ // Trivial case, non-pointer iterator.
+ std::list<char> text = {'f', 'o', 'o'};
+ ArrayBuilder<char> builder = heapArrayBuilder<char>(5);
+ builder.add('<');
+ builder.addAll(text);
+ builder.add('>');
+ auto array = builder.finish();
+ EXPECT_EQ("<foo>", std::string(array.begin(), array.end()));
+ }
+
+ {
+ // Complex case.
+ std::string strs[] = {"foo", "bar", "baz"};
+ ArrayBuilder<std::string> builder = heapArrayBuilder<std::string>(5);
+ builder.add("qux");
+ builder.addAll(strs, strs + 3);
+ builder.add("quux");
+ auto array = builder.finish();
+ EXPECT_EQ("qux", array[0]);
+ EXPECT_EQ("foo", array[1]);
+ EXPECT_EQ("bar", array[2]);
+ EXPECT_EQ("baz", array[3]);
+ EXPECT_EQ("quux", array[4]);
+ }
+
+ {
+ // Complex case, noexcept.
+ TestNoexceptObject::count = 0;
+ TestNoexceptObject::copiedCount = 0;
+ TestNoexceptObject objs[3];
+ EXPECT_EQ(3, TestNoexceptObject::count);
+ EXPECT_EQ(0, TestNoexceptObject::copiedCount);
+ ArrayBuilder<TestNoexceptObject> builder = heapArrayBuilder<TestNoexceptObject>(3);
+ EXPECT_EQ(3, TestNoexceptObject::count);
+ EXPECT_EQ(0, TestNoexceptObject::copiedCount);
+ builder.addAll(objs, objs + 3);
+ EXPECT_EQ(3, TestNoexceptObject::count);
+ EXPECT_EQ(3, TestNoexceptObject::copiedCount);
+ auto array = builder.finish();
+ EXPECT_EQ(3, TestNoexceptObject::count);
+ EXPECT_EQ(3, TestNoexceptObject::copiedCount);
+ }
+ EXPECT_EQ(0, TestNoexceptObject::count);
+ EXPECT_EQ(0, TestNoexceptObject::copiedCount);
+
+ {
+ // Complex case, exceptions possible.
+ TestObject::count = 0;
+ TestObject::copiedCount = 0;
+ TestObject::throwAt = -1;
+ TestObject objs[3];
+ EXPECT_EQ(3, TestObject::count);
+ EXPECT_EQ(0, TestObject::copiedCount);
+ ArrayBuilder<TestObject> builder = heapArrayBuilder<TestObject>(3);
+ EXPECT_EQ(3, TestObject::count);
+ EXPECT_EQ(0, TestObject::copiedCount);
+ builder.addAll(objs, objs + 3);
+ EXPECT_EQ(3, TestObject::count);
+ EXPECT_EQ(3, TestObject::copiedCount);
+ auto array = builder.finish();
+ EXPECT_EQ(3, TestObject::count);
+ EXPECT_EQ(3, TestObject::copiedCount);
+ }
+ EXPECT_EQ(0, TestObject::count);
+ EXPECT_EQ(0, TestObject::copiedCount);
+
+ {
+ // Complex case, exceptions occur.
+ TestObject::count = 0;
+ TestObject::copiedCount = 0;
+ TestObject::throwAt = -1;
+ TestObject objs[3];
+ EXPECT_EQ(3, TestObject::count);
+ EXPECT_EQ(0, TestObject::copiedCount);
+
+ TestObject::throwAt = 1;
+
+ ArrayBuilder<TestObject> builder = heapArrayBuilder<TestObject>(3);
+ EXPECT_EQ(3, TestObject::count);
+ EXPECT_EQ(0, TestObject::copiedCount);
+
+ EXPECT_ANY_THROW(builder.addAll(objs, objs + 3));
+ TestObject::throwAt = -1;
+
+ EXPECT_EQ(3, TestObject::count);
+ EXPECT_EQ(0, TestObject::copiedCount);
+ }
+ EXPECT_EQ(0, TestObject::count);
+ EXPECT_EQ(0, TestObject::copiedCount);
+}
+
+} // namespace
+} // namespace kj
diff --git a/c++/src/kj/array.c++ b/c++/src/kj/array.c++
index aec257a..f824ee6 100644
--- a/c++/src/kj/array.c++
+++ b/c++/src/kj/array.c++
@@ -22,7 +22,87 @@
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "array.h"
+#include <iostream>
namespace kj {
+ArrayDisposer::~ArrayDisposer() {}
+
+namespace internal {
+
+struct HeapArrayDisposer::ExceptionGuard {
+ byte* pos;
+ size_t elementSize;
+ size_t elementCount;
+ size_t constructedCount;
+ void (*destroyElement)(void*);
+
+ ExceptionGuard(void* ptr, size_t elementSize, size_t elementCount,
+ void (*destroyElement)(void*))
+ : pos(reinterpret_cast<byte*>(ptr) + elementSize * elementCount),
+ elementSize(elementSize), elementCount(elementCount),
+ destroyElement(destroyElement) {}
+
+ ~ExceptionGuard() {
+ if (pos != nullptr) {
+ destroyAll();
+ operator delete(pos);
+ }
+ }
+
+ void destroyAll() {
+ while (elementCount > 0) {
+ pos -= elementSize;
+ --elementCount;
+ destroyElement(pos);
+ }
+ }
+};
+
+void* HeapArrayDisposer::allocateImpl(size_t elementSize, size_t elementCount, size_t capacity,
+ void (*constructElement)(void*),
+ void (*destroyElement)(void*)) {
+ void* result = operator new(elementSize * capacity);
+
+ if (constructElement == nullptr) {
+ // Nothing to do.
+ } else if (destroyElement == nullptr) {
+ byte* pos = reinterpret_cast<byte*>(result);
+ while (elementCount > 0) {
+ constructElement(pos);
+ pos += elementSize;
+ --elementCount;
+ }
+ } else {
+ ExceptionGuard guard(result, elementSize, 0, destroyElement);
+ while (guard.elementCount < elementCount) {
+ constructElement(guard.pos);
+ guard.pos += elementSize;
+ ++guard.elementCount;
+ }
+ guard.pos = nullptr;
+ }
+
+ return result;
+}
+
+void HeapArrayDisposer::disposeImpl(
+ void* firstElement, size_t elementSize, size_t elementCount, size_t capacity,
+ void (*destroyElement)(void*)) const {
+ // Note that capacity is ignored since operator delete() doesn't care about it.
+
+ if (destroyElement == nullptr) {
+ operator delete(firstElement);
+ } else {
+ ExceptionGuard guard(firstElement, elementSize, elementCount, destroyElement);
+ guard.destroyAll();
+
+ // If an exception is thrown, we'll continue the destruction process in ExceptionGuard's
+ // destructor. If _that_ throws an exception, the program terminates according to C++ rules.
+ }
+}
+
+const HeapArrayDisposer HeapArrayDisposer::instance = HeapArrayDisposer();
+
+} // namespace internal
} // namespace kj
diff --git a/c++/src/kj/array.h b/c++/src/kj/array.h
index b155cac..9a0b548 100644
--- a/c++/src/kj/array.h
+++ b/c++/src/kj/array.h
@@ -25,29 +25,62 @@
#define KJ_ARRAY_H_
#include "common.h"
-#include "memory.h"
#include <string.h>
namespace kj {
// =======================================================================================
+// ArrayDisposer -- Implementation details.
+
+class ArrayDisposer {
+ // Much like Disposer from memory.h.
+
+protected:
+ virtual ~ArrayDisposer();
+
+ virtual void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount,
+ size_t capacity, void (*destroyElement)(void*)) const = 0;
+ // Disposes of the array. `destroyElement` invokes the destructor of each element, or is nullptr
+ // if the elements have trivial destructors. `capacity` is the amount of space that was
+ // allocated while `elementCount` is the number of elements that were actually constructed;
+ // these are always the same number for Array<T> but may be different when using ArrayBuilder<T>.
+
+public:
+
+ template <typename T>
+ void dispose(T* firstElement, size_t elementCount, size_t capacity) const;
+ // Helper wrapper around disposeImpl().
+ //
+ // Callers must not call dispose() on the same array twice, even if the first call throws
+ // an exception.
+
+private:
+ template <typename T, bool hasTrivialDestructor = __has_trivial_destructor(T)>
+ struct Dispose_;
+};
+
+// =======================================================================================
// Array
template <typename T>
class Array {
- // An owned array which will automatically be deleted in the destructor. Can be moved, but not
- // copied.
+ // An owned array which will automatically be disposed of (using an ArrayDisposer) in the
+ // destructor. Can be moved, but not copied. Much like Own<T>, but for arrays rather than
+ // single objects.
public:
inline Array(): ptr(nullptr), size_(0) {}
inline Array(decltype(nullptr)): ptr(nullptr), size_(0) {}
- inline Array(Array&& other) noexcept: ptr(other.ptr), size_(other.size_) {
+ inline Array(Array&& other) noexcept
+ : ptr(other.ptr), size_(other.size_), disposer(other.disposer) {
other.ptr = nullptr;
other.size_ = 0;
}
+ inline Array(T* firstElement, size_t size, const ArrayDisposer& disposer)
+ : ptr(firstElement), size_(size), disposer(&disposer) {}
KJ_DISALLOW_COPY(Array);
- inline ~Array() noexcept { delete[] ptr; }
+ inline ~Array() noexcept { dispose(); }
inline operator ArrayPtr<T>() {
return ArrayPtr<T>(ptr, size_);
@@ -65,10 +98,14 @@
return ptr[index];
}
- inline T* begin() const { return ptr; }
- inline T* end() const { return ptr + size_; }
- inline T& front() const { return *ptr; }
- inline T& back() const { return *(ptr + size_ - 1); }
+ inline const T* begin() const { return ptr; }
+ inline const T* end() const { return ptr + size_; }
+ inline const T& front() const { return *ptr; }
+ inline const T& back() const { return *(ptr + size_ - 1); }
+ inline T* begin() { return ptr; }
+ inline T* end() { return ptr + size_; }
+ inline T& front() { return *ptr; }
+ inline T& back() { return *(ptr + size_ - 1); }
inline ArrayPtr<T> slice(size_t start, size_t end) {
KJ_INLINE_DPRECOND(start <= end && end <= size_, "Out-of-bounds Array::slice().");
@@ -83,16 +120,15 @@
inline bool operator!=(decltype(nullptr)) const { return size_ != 0; }
inline Array& operator=(decltype(nullptr)) {
- delete[] ptr;
- ptr = nullptr;
- size_ = 0;
+ dispose();
return *this;
}
inline Array& operator=(Array&& other) {
- delete[] ptr;
+ dispose();
ptr = other.ptr;
size_ = other.size_;
+ disposer = other.disposer;
other.ptr = nullptr;
other.size_ = 0;
return *this;
@@ -101,20 +137,56 @@
private:
T* ptr;
size_t size_;
+ const ArrayDisposer* disposer;
- inline explicit Array(size_t size): ptr(new T[size]), size_(size) {}
- inline Array(T* ptr, size_t size): ptr(ptr), size_(size) {}
-
- template <typename U>
- friend Array<U> newArray(size_t size);
-
- template <typename U>
- friend class ArrayBuilder;
+ inline void dispose() {
+ // Make sure that if an exception is thrown, we are left with a null ptr, so we won't possibly
+ // dispose again.
+ T* ptrCopy = ptr;
+ size_t sizeCopy = size_;
+ if (ptrCopy != nullptr) {
+ ptr = nullptr;
+ size_ = 0;
+ disposer->dispose(ptrCopy, sizeCopy, sizeCopy);
+ }
+ }
};
+namespace internal {
+
+class HeapArrayDisposer final: public ArrayDisposer {
+public:
+ static void* allocateImpl(size_t elementSize, size_t elementCount, size_t capacity,
+ void (*constructElement)(void*), void (*destroyElement)(void*));
+ // Allocates and constructs the array. Both function pointers are null if the constructor is
+ // trivial, otherwise destroyElement is null if the constructor doesn't throw.
+
+ virtual void disposeImpl(void* firstElement, size_t elementSize, size_t elementCount,
+ size_t capacity, void (*destroyElement)(void*)) const override;
+
+ template <typename T>
+ static T* allocate(size_t count);
+ template <typename T>
+ static T* allocateUninitialized(size_t count);
+
+ static const HeapArrayDisposer instance;
+
+private:
+ template <typename T, bool hasTrivialConstructor = __has_trivial_constructor(T),
+ bool hasNothrowConstructor = __has_nothrow_constructor(T)>
+ struct Allocate_;
+
+ struct ExceptionGuard;
+};
+
+} // namespace internal
+
template <typename T>
-inline Array<T> newArray(size_t size) {
- return Array<T>(size);
+inline Array<T> heapArray(size_t size) {
+ // Much like `heap<T>()` from memory.h, allocates a new array on the heap.
+
+ return Array<T>(internal::HeapArrayDisposer::allocate<T>(size), size,
+ internal::HeapArrayDisposer::instance);
}
// =======================================================================================
@@ -122,53 +194,90 @@
template <typename T>
class ArrayBuilder {
- // TODO(cleanup): This class doesn't work for non-primitive types because Slot is not
- // constructable. Giving Slot a constructor/destructor means arrays of it have to be tagged
- // so operator delete can run the destructors. If we reinterpret_cast the array to an array
- // of T and delete it as that type, operator delete gets very upset.
- //
- // Perhaps we should bite the bullet and make the Array family do manual memory allocation,
- // bypassing the rather-stupid C++ array new/delete operators which store a redundant copy of
- // the size anyway.
-
- union Slot {
- T value;
- char dummy;
- };
- static_assert(sizeof(Slot) == sizeof(T), "union is bigger than content?");
+ // Class which lets you build an Array<T> specifying the exact constructor arguments for each
+ // element, rather than starting by default-constructing them.
public:
- explicit ArrayBuilder(size_t size): ptr(new Slot[size]), pos(ptr), endPtr(ptr + size) {}
- ~ArrayBuilder() {
- for (Slot* p = ptr; p < pos; ++p) {
- p->value.~T();
- }
- delete [] ptr;
+ ArrayBuilder(): ptr(nullptr), pos(nullptr), endPtr(nullptr) {}
+ ArrayBuilder(decltype(nullptr)): ptr(nullptr), pos(nullptr), endPtr(nullptr) {}
+ explicit ArrayBuilder(T* firstElement, size_t capacity, const ArrayDisposer& disposer)
+ : ptr(firstElement), pos(firstElement), endPtr(firstElement + capacity),
+ disposer(&disposer) {}
+ ArrayBuilder(ArrayBuilder&& other)
+ : ptr(other.ptr), pos(other.pos), endPtr(other.endPtr), disposer(other.disposer) {
+ other.ptr = nullptr;
+ other.pos = nullptr;
+ other.endPtr = nullptr;
+ }
+ KJ_DISALLOW_COPY(ArrayBuilder);
+ inline ~ArrayBuilder() { dispose(); }
+
+ inline operator ArrayPtr<T>() {
+ return arrayPtr(ptr, pos);
+ }
+ inline operator ArrayPtr<const T>() const {
+ return arrayPtr(ptr, pos);
+ }
+ inline ArrayPtr<T> asPtr() {
+ return arrayPtr(ptr, pos);
+ }
+
+ inline size_t size() const { return pos - ptr; }
+ inline size_t capacity() const { return endPtr - ptr; }
+ inline T& operator[](size_t index) const {
+ KJ_INLINE_DPRECOND(index < pos - ptr, "Out-of-bounds Array access.");
+ return ptr[index];
+ }
+
+ inline const T* begin() const { return ptr; }
+ inline const T* end() const { return pos; }
+ inline const T& front() const { return *ptr; }
+ inline const T& back() const { return *(pos - 1); }
+ inline T* begin() { return ptr; }
+ inline T* end() { return pos; }
+ inline T& front() { return *ptr; }
+ inline T& back() { return *(pos - 1); }
+
+ ArrayBuilder& operator=(ArrayBuilder&& other) {
+ dispose();
+ ptr = other.ptr;
+ pos = other.pos;
+ endPtr = other.endPtr;
+ disposer = other.disposer;
+ other.ptr = nullptr;
+ other.pos = nullptr;
+ other.endPtr = nullptr;
+ return *this;
+ }
+ ArrayBuilder& operator=(decltype(nullptr)) {
+ dispose();
+ return *this;
}
template <typename... Params>
void add(Params&&... params) {
KJ_INLINE_DPRECOND(pos < endPtr, "Added too many elements to ArrayBuilder.");
- new(&pos->value) T(kj::fwd<Params>(params)...);
+ ctor(*pos, kj::fwd<Params>(params)...);
++pos;
}
template <typename Container>
void addAll(Container&& container) {
- Slot* __restrict__ pos_ = pos;
- auto i = container.begin();
- auto end = container.end();
- while (i != end) {
- pos_++->value = *i++;
- }
- pos = pos_;
+ addAll(container.begin(), container.end());
}
+ template <typename Iterator>
+ void addAll(Iterator start, Iterator end);
+
Array<T> finish() {
- // We could allow partial builds if Array<T> used a deleter callback, but that would make
- // Array<T> bigger for no benefit most of the time.
+ // We could safely remove this check as long as HeapArrayDisposer relies on operator delete
+ // (which doesn't need to know the original capacity) or if we created a custom disposer for
+ // ArrayBuilder which stores the capacity in a prefix. But that would mean we can't allow
+ // arbitrary disposers with ArrayBuilder in the future, and anyway this check might catch bugs.
+ // Probably we should just create a new Vector-like data structure if we want to allow building
+ // of arrays without knowing the final size in advance.
KJ_INLINE_DPRECOND(pos == endPtr, "ArrayBuilder::finish() called prematurely.");
- Array<T> result(reinterpret_cast<T*>(ptr), pos - ptr);
+ Array<T> result(reinterpret_cast<T*>(ptr), pos - ptr, internal::HeapArrayDisposer::instance);
ptr = nullptr;
pos = nullptr;
endPtr = nullptr;
@@ -176,11 +285,183 @@
}
private:
- Slot* ptr;
- Slot* pos;
- Slot* endPtr;
+ T* ptr;
+ T* pos;
+ T* endPtr;
+ const ArrayDisposer* disposer;
+
+ inline void dispose() {
+ // Make sure that if an exception is thrown, we are left with a null ptr, so we won't possibly
+ // dispose again.
+ T* ptrCopy = ptr;
+ T* posCopy = pos;
+ T* endCopy = endPtr;
+ if (ptrCopy != nullptr) {
+ ptr = nullptr;
+ pos = nullptr;
+ endPtr = nullptr;
+ disposer->dispose(ptrCopy, posCopy - ptrCopy, endCopy - ptrCopy);
+ }
+ }
};
+template <typename T>
+inline ArrayBuilder<T> heapArrayBuilder(size_t size) {
+ // Like `heapArray<T>()` but does not default-construct the elements. You must construct them
+ // manually by calling `add()`.
+
+ return ArrayBuilder<T>(internal::HeapArrayDisposer::allocateUninitialized<T>(size), size,
+ internal::HeapArrayDisposer::instance);
+}
+
+// =======================================================================================
+// Inline implementation details
+
+template <typename T>
+struct ArrayDisposer::Dispose_<T, true> {
+ static void dispose(T* firstElement, size_t elementCount, size_t capacity,
+ const ArrayDisposer& disposer) {
+ disposer.disposeImpl(firstElement, sizeof(T), elementCount, capacity, nullptr);
+ }
+};
+template <typename T>
+struct ArrayDisposer::Dispose_<T, false> {
+ static void destruct(void* ptr) {
+ kj::dtor(*reinterpret_cast<T*>(ptr));
+ }
+
+ static void dispose(T* firstElement, size_t elementCount, size_t capacity,
+ const ArrayDisposer& disposer) {
+ disposer.disposeImpl(firstElement, sizeof(T), elementCount, capacity, &destruct);
+ }
+};
+
+template <typename T>
+void ArrayDisposer::dispose(T* firstElement, size_t elementCount, size_t capacity) const {
+ Dispose_<T>::dispose(firstElement, elementCount, capacity, *this);
+}
+
+namespace internal {
+
+template <typename T>
+struct HeapArrayDisposer::Allocate_<T, true, true> {
+ static T* allocate(size_t elementCount, size_t capacity) {
+ return reinterpret_cast<T*>(allocateImpl(
+ sizeof(T), elementCount, capacity, nullptr, nullptr));
+ }
+};
+template <typename T>
+struct HeapArrayDisposer::Allocate_<T, false, true> {
+ static void construct(void* ptr) {
+ kj::ctor(*reinterpret_cast<T*>(ptr));
+ }
+ static T* allocate(size_t elementCount, size_t capacity) {
+ return reinterpret_cast<T*>(allocateImpl(
+ sizeof(T), elementCount, capacity, &construct, nullptr));
+ }
+};
+template <typename T>
+struct HeapArrayDisposer::Allocate_<T, false, false> {
+ static void construct(void* ptr) {
+ kj::ctor(*reinterpret_cast<T*>(ptr));
+ }
+ static void destruct(void* ptr) {
+ kj::dtor(*reinterpret_cast<T*>(ptr));
+ }
+ static T* allocate(size_t elementCount, size_t capacity) {
+ return reinterpret_cast<T*>(allocateImpl(
+ sizeof(T), elementCount, capacity, &construct, &destruct));
+ }
+};
+
+template <typename T>
+T* HeapArrayDisposer::allocate(size_t count) {
+ return Allocate_<T>::allocate(count, count);
+}
+
+template <typename T>
+T* HeapArrayDisposer::allocateUninitialized(size_t count) {
+ return Allocate_<T, true, true>::allocate(0, count);
+}
+
+template <typename Element, typename Iterator,
+ bool trivial = __has_trivial_copy(Element) && __has_trivial_assign(Element)>
+struct CopyConstructArray_;
+
+template <typename T>
+struct CopyConstructArray_<T, T*, true> {
+ static inline T* apply(T* __restrict__ pos, T* start, T* end) {
+ memcpy(pos, start, end - start);
+ return pos + (end - start);
+ }
+};
+
+template <typename T>
+struct CopyConstructArray_<T, const T*, true> {
+ static inline T* apply(T* __restrict__ pos, const T* start, const T* end) {
+ memcpy(pos, start, end - start);
+ return pos + (end - start);
+ }
+};
+
+template <typename T, typename Iterator>
+struct CopyConstructArray_<T, Iterator, true> {
+ static inline T* apply(T* __restrict__ pos, Iterator start, Iterator end) {
+ // Since both the copy constructor and assignment operator are trivial, we know that assignment
+ // is equivalent to copy-constructing. So we can make this case somewhat easier for the
+ // compiler to optimize.
+ while (start != end) {
+ *pos++ = *start++;
+ }
+ return pos;
+ }
+};
+
+template <typename T, typename Iterator>
+struct CopyConstructArray_<T, Iterator, false> {
+ struct ExceptionGuard {
+ T* start;
+ T* pos;
+ inline explicit ExceptionGuard(T* pos): start(pos), pos(pos) {}
+ ~ExceptionGuard() {
+ while (pos > start) {
+ dtor(*--pos);
+ }
+ }
+ };
+
+ static T* apply(T* __restrict__ pos, Iterator start, Iterator end) {
+ if (noexcept(T(instance<const T&>()))) {
+ while (start != end) {
+ ctor(*pos++, upcast<const T&>(*start++));
+ }
+ return pos;
+ } else {
+ // Crap. This is complicated.
+ ExceptionGuard guard(pos);
+ while (start != end) {
+ ctor(*guard.pos, upcast<const T&>(*start++));
+ ++guard.pos;
+ }
+ guard.start = guard.pos;
+ return guard.pos;
+ }
+ }
+};
+
+template <typename T, typename Iterator>
+inline T* copyConstructArray(T* dst, Iterator start, Iterator end) {
+ return CopyConstructArray_<T, RemoveReference<Iterator>>::apply(dst, start, end);
+}
+
+} // namespace internal
+
+template <typename T>
+template <typename Iterator>
+void ArrayBuilder<T>::addAll(Iterator start, Iterator end) {
+ pos = internal::copyConstructArray(pos, start, end);
+}
+
} // namespace kj
#endif // KJ_ARRAY_H_
diff --git a/c++/src/kj/common.h b/c++/src/kj/common.h
index 7353c27..7f817e4 100644
--- a/c++/src/kj/common.h
+++ b/c++/src/kj/common.h
@@ -145,7 +145,7 @@
bool name##_isOnStack = name##_size <= (minStack); \
type name##_stack[minStack]; \
::kj::Array<type> name##_heap = name##_isOnStack ? \
- nullptr : kj::newArray<type>(name##_size); \
+ nullptr : kj::heapArray<type>(name##_size); \
::kj::ArrayPtr<type> name = name##_isOnStack ? \
kj::arrayPtr(name##_stack, name##_size) : name##_heap
#else
@@ -154,7 +154,7 @@
bool name##_isOnStack = name##_size <= (maxStack); \
type name##_stack[name##_isOnStack ? size : 0]; \
::kj::Array<type> name##_heap = name##_isOnStack ? \
- nullptr : kj::newArray<type>(name##_size); \
+ nullptr : kj::heapArray<type>(name##_size); \
::kj::ArrayPtr<type> name = name##_isOnStack ? \
kj::arrayPtr(name##_stack, name##_size) : name##_heap
#endif
diff --git a/c++/src/kj/exception.c++ b/c++/src/kj/exception.c++
index 5bf7764..fc5bb47 100644
--- a/c++/src/kj/exception.c++
+++ b/c++/src/kj/exception.c++
@@ -98,7 +98,7 @@
}
}
- Array<Array<char>> contextText = newArray<Array<char>>(contextDepth);
+ Array<Array<char>> contextText = heapArray<Array<char>>(contextDepth);
contextDepth = 0;
contextPtr = &context;
diff --git a/c++/src/kj/exception.h b/c++/src/kj/exception.h
index 333691e..75e71d6 100644
--- a/c++/src/kj/exception.h
+++ b/c++/src/kj/exception.h
@@ -25,6 +25,7 @@
#define KJ_EXCEPTION_H_
#include <exception>
+#include "memory.h"
#include "array.h"
namespace kj {
diff --git a/c++/src/kj/io.c++ b/c++/src/kj/io.c++
index f7c8b07..23742ae 100644
--- a/c++/src/kj/io.c++
+++ b/c++/src/kj/io.c++
@@ -52,7 +52,7 @@
// =======================================================================================
BufferedInputStreamWrapper::BufferedInputStreamWrapper(InputStream& inner, ArrayPtr<byte> buffer)
- : inner(inner), ownedBuffer(buffer == nullptr ? newArray<byte>(8192) : nullptr),
+ : inner(inner), ownedBuffer(buffer == nullptr ? heapArray<byte>(8192) : nullptr),
buffer(buffer == nullptr ? ownedBuffer : buffer) {}
BufferedInputStreamWrapper::~BufferedInputStreamWrapper() {}
@@ -118,7 +118,7 @@
BufferedOutputStreamWrapper::BufferedOutputStreamWrapper(OutputStream& inner, ArrayPtr<byte> buffer)
: inner(inner),
- ownedBuffer(buffer == nullptr ? newArray<byte>(8192) : nullptr),
+ ownedBuffer(buffer == nullptr ? heapArray<byte>(8192) : nullptr),
buffer(buffer == nullptr ? ownedBuffer : buffer),
bufferPos(this->buffer.begin()) {}
diff --git a/c++/src/kj/logging.c++ b/c++/src/kj/logging.c++
index fd5df37..cb2c8be 100644
--- a/c++/src/kj/logging.c++
+++ b/c++/src/kj/logging.c++
@@ -157,7 +157,7 @@
totalSize += argValues[i].size();
}
- ArrayBuilder<char> result(totalSize);
+ ArrayBuilder<char> result = heapArrayBuilder<char>(totalSize);
switch (style) {
case LOG:
diff --git a/c++/src/kj/memory.h b/c++/src/kj/memory.h
index f90afa1..cd47553 100644
--- a/c++/src/kj/memory.h
+++ b/c++/src/kj/memory.h
@@ -29,27 +29,40 @@
namespace kj {
// =======================================================================================
+// Disposer -- Implementation details.
class Disposer {
- // Abstract interface for a thing that disposes of some other object. Often, it makes sense to
- // decouple an object from the knowledge of how to dispose of it.
+ // Abstract interface for a thing that "disposes" of objects, where "disposing" usually means
+ // calling the destructor followed by freeing the underlying memory. `Own<T>` encapsulates an
+ // object pointer with corresponding Disposer.
+ //
+ // Few developers will ever touch this interface. It is primarily useful for those implementing
+ // custom memory allocators.
protected:
virtual ~Disposer();
+ virtual void disposeImpl(void* pointer) const = 0;
+ // Disposes of the object, given a pointer to the beginning of the object. If the object is
+ // polymorphic, this pointer is determined by dynamic_cast<void*>(). For non-polymorphic types,
+ // Own<T> does not allow any casting, so the pointer exactly matches the original one given to
+ // Own<T>.
+
public:
- virtual void dispose(void* interiorPointer) = 0;
- // Disposes of the object that this Disposer owns, and possibly disposes of the disposer itself.
+
+ template <typename T>
+ void dispose(T* object) const;
+ // Helper wrapper around disposeImpl().
//
- // Callers must assume that the Disposer itself is no longer valid once this returns -- e.g. it
- // might delete itself. Callers must in particular be sure not to call the Disposer again even
- // when dispose() throws an exception.
+ // If T is polymorphic, calls `disposeImpl(dynamic_cast<void*>(object))`, otherwise calls
+ // `disposeImpl(upcast<void*>(object))`.
//
- // `interiorPointer` points somewhere inside of the object -- NOT necessarily at the beginning,
- // especially in the presence of multiple inheritance. Most implementations should ignore the
- // pointer, though a tricky memory allocator could get away with sharing one Disposer among
- // multiple objects if it can figure out how to find the beginning of the object given an
- // arbitrary interior pointer.
+ // Callers must not call dispose() on the same pointer twice, even if the first call throws
+ // an exception.
+
+private:
+ template <typename T, bool polymorphic = __is_polymorphic(T)>
+ struct Dispose_;
};
// =======================================================================================
@@ -64,11 +77,12 @@
// This is much like std::unique_ptr, except:
// - You cannot release(). An owned object is not necessarily allocated with new (see next
// point), so it would be hard to use release() correctly.
- // - The deleter is made polymorphic by virtual call rather than by template. This is a much
- // more powerful default -- it allows any random module to decide to use a custom allocator.
- // This could be accomplished with unique_ptr by forcing everyone to use e.g.
- // std::unique_ptr<T, kj::Disposer&>, but at that point we've lost basically any benefit
- // of interoperating with std::unique_ptr anyway.
+ // - The deleter is made polymorphic by virtual call rather than by template. This is much
+ // more powerful -- it allows the use of custom allocators, freelists, etc. This could
+ // _almost_ be accomplished with unique_ptr by forcing everyone to use something like
+ // std::unique_ptr<T, kj::Deleter>, except that things get hairy in the presence of multiple
+ // inheritance and upcasting, and anyway if you force everyone to use a custom deleter
+ // then you've lost any benefit to interoperating with the "standard" unique_ptr.
public:
Own(const Own& other) = delete;
@@ -76,8 +90,12 @@
: disposer(other.disposer), ptr(other.ptr) { other.ptr = nullptr; }
template <typename U>
inline Own(Own<U>&& other) noexcept
- : disposer(other.disposer), ptr(other.ptr) { other.ptr = nullptr; }
- inline Own(T* ptr, Disposer* disposer) noexcept: disposer(disposer), ptr(ptr) {}
+ : disposer(other.disposer), ptr(other.ptr) {
+ static_assert(__is_polymorphic(T),
+ "Casting owned pointers requires that the target type is polymorphic.");
+ other.ptr = nullptr;
+ }
+ inline Own(T* ptr, const Disposer& disposer) noexcept: disposer(&disposer), ptr(ptr) {}
~Own() noexcept { dispose(); }
@@ -99,13 +117,13 @@
inline operator const T*() const { return ptr; }
private:
- Disposer* disposer; // Only valid if ptr != nullptr.
+ const Disposer* disposer; // Only valid if ptr != nullptr.
T* ptr;
inline void dispose() {
// Make sure that if an exception is thrown, we are left with a null ptr, so we won't possibly
// dispose again.
- void* ptrCopy = ptr;
+ T* ptrCopy = ptr;
if (ptrCopy != nullptr) {
ptr = nullptr;
disposer->dispose(ptrCopy);
@@ -116,26 +134,50 @@
namespace internal {
template <typename T>
-class HeapValue final: public Disposer {
+class HeapDisposer final: public Disposer {
public:
- template <typename... Params>
- inline HeapValue(Params&&... params): value(kj::fwd<Params>(params)...) {}
+ virtual void disposeImpl(void* pointer) const override { delete reinterpret_cast<T*>(pointer); }
- virtual void dispose(void*) override { delete this; }
-
- T value;
+ static const HeapDisposer instance;
};
+template <typename T>
+const HeapDisposer<T> HeapDisposer<T>::instance = HeapDisposer<T>();
+
} // namespace internal
template <typename T, typename... Params>
Own<T> heap(Params&&... params) {
// heap<T>(...) allocates a T on the heap, forwarding the parameters to its constructor. The
// exact heap implementation is unspecified -- for now it is operator new, but you should not
- // assume anything.
+ // assume this. (Since we know the object size at delete time, we could actually implement an
+ // allocator that is more efficient than operator new.)
- auto result = new internal::HeapValue<T>(kj::fwd<Params>(params)...);
- return Own<T>(&result->value, result);
+ return Own<T>(new T(kj::fwd<Params>(params)...), internal::HeapDisposer<T>::instance);
+}
+
+// =======================================================================================
+// Inline implementation details
+
+template <typename T>
+struct Disposer::Dispose_<T, true> {
+ static void dispose(T* object, const Disposer& disposer) {
+ // Note that dynamic_cast<void*> does not require RTTI to be enabled, because the offset to
+ // the top of the object is in the vtable -- as it obviously needs to be to correctly implement
+ // operator delete.
+ disposer.disposeImpl(dynamic_cast<void*>(object));
+ }
+};
+template <typename T>
+struct Disposer::Dispose_<T, false> {
+ static void dispose(T* object, const Disposer& disposer) {
+ disposer.disposeImpl(static_cast<void*>(object));
+ }
+};
+
+template <typename T>
+void Disposer::dispose(T* object) const {
+ Dispose_<T>::dispose(object, *this);
}
} // namespace kj
diff --git a/c++/src/kj/string.c++ b/c++/src/kj/string.c++
index 88dcf37..77c9d9a 100644
--- a/c++/src/kj/string.c++
+++ b/c++/src/kj/string.c++
@@ -25,11 +25,11 @@
namespace kj {
-String::String(const char* value): content(newArray<char>(strlen(value) + 1)) {
+String::String(const char* value): content(heapArray<char>(strlen(value) + 1)) {
strcpy(content.begin(), value);
}
-String::String(const char* value, size_t length): content(newArray<char>(length + 1)) {
+String::String(const char* value, size_t length): content(heapArray<char>(length + 1)) {
memcpy(content.begin(), value, length);
content[length] = '\0';
}
diff --git a/c++/src/kj/util.h b/c++/src/kj/util.h
index 1ca5021..e707d7c 100644
--- a/c++/src/kj/util.h
+++ b/c++/src/kj/util.h
@@ -98,7 +98,7 @@
Array<T> iterableToArray(Container&& a) {
// Converts an arbitrary iterable container into an array of the given element type.
- Array<T> result = newArray<T>(a.size());
+ Array<T> result = heapArray<T>(a.size());
auto i = a.iterator();
auto end = a.end();
T* __restrict__ ptr = result.begin();
@@ -146,7 +146,7 @@
// Eclipse reports a bogus error on `size()`.
Array<Element> result;
#else
- Array<Element> result = newArray<Element>(sum({params.size()...}));
+ Array<Element> result = heapArray<Element>(sum({params.size()...}));
#endif
fill(result.begin(), std::forward<Params>(params)...);
return result;
@@ -230,7 +230,7 @@
size += pieces[i].size();
}
- Array<char> result = newArray<char>(size);
+ Array<char> result = heapArray<char>(size);
char* pos = result.begin();
for (size_t i = 0; i < arr.size(); i++) {
if (i > 0) {
@@ -255,7 +255,7 @@
template <typename T, typename Func>
auto mapArray(T&& arr, Func&& func) -> Array<decltype(func(arr[0]))> {
// TODO(cleanup): Use ArrayBuilder.
- Array<decltype(func(arr[0]))> result = newArray<decltype(func(arr[0]))>(arr.size());
+ Array<decltype(func(arr[0]))> result = heapArray<decltype(func(arr[0]))>(arr.size());
size_t pos = 0;
for (auto& element: arr) {
result[pos++] = func(element);