diff --git a/core/matrix/coo.cpp b/core/matrix/coo.cpp index 1a067873842..67b7dd1ea3d 100644 --- a/core/matrix/coo.cpp +++ b/core/matrix/coo.cpp @@ -19,6 +19,7 @@ #include +#include "core/base/device_matrix_data_kernels.hpp" #include "core/components/absolute_array_kernels.hpp" #include "core/components/fill_array_kernels.hpp" #include "core/components/format_conversion_kernels.hpp" @@ -43,6 +44,7 @@ GKO_REGISTER_OPERATION(inplace_absolute_array, components::inplace_absolute_array); GKO_REGISTER_OPERATION(outplace_absolute_array, components::outplace_absolute_array); +GKO_REGISTER_OPERATION(aos_to_soa, components::aos_to_soa); } // anonymous namespace @@ -180,15 +182,39 @@ void Coo::resize(dim<2> new_size, size_type nnz) template void Coo::read(const mat_data& data) { - this->read(device_mat_data::create_from_host(this->get_executor(), data)); + auto size = data.size; + auto exec = this->get_executor(); + this->set_size(size); + this->row_idxs_.resize_and_reset(data.nonzeros.size()); + this->col_idxs_.resize_and_reset(data.nonzeros.size()); + this->values_.resize_and_reset(data.nonzeros.size()); + device_mat_data view{exec, size, this->row_idxs_.as_view(), + this->col_idxs_.as_view(), this->values_.as_view()}; + const auto host_data = + make_array_view(exec->get_master(), data.nonzeros.size(), + const_cast*>( + data.nonzeros.data())); + exec->run( + coo::make_aos_to_soa(*make_temporary_clone(exec, &host_data), view)); } template void Coo::read(const device_mat_data& data) { - // make a copy, read the data in - this->read(device_mat_data{this->get_executor(), data}); + this->set_size(data.get_size()); + // copy the arrays from device matrix data into the arrays of + // this. Compared to the read(device_mat_data&&) version, the internal + // arrays keep their current ownership status + this->values_ = make_const_array_view(data.get_executor(), + data.get_num_stored_elements(), + data.get_const_values()); + this->col_idxs_ = make_const_array_view(data.get_executor(), + data.get_num_stored_elements(), + data.get_const_col_idxs()); + this->row_idxs_ = make_const_array_view(data.get_executor(), + data.get_num_stored_elements(), + data.get_const_row_idxs()); } diff --git a/core/matrix/csr.cpp b/core/matrix/csr.cpp index 2a56f43f813..96f72059ce5 100644 --- a/core/matrix/csr.cpp +++ b/core/matrix/csr.cpp @@ -95,6 +95,7 @@ GKO_REGISTER_OPERATION(inv_scale, csr::inv_scale); GKO_REGISTER_OPERATION(add_scaled_identity, csr::add_scaled_identity); GKO_REGISTER_OPERATION(check_diagonal_entries, csr::check_diagonal_entries_exist); +GKO_REGISTER_OPERATION(aos_to_soa, components::aos_to_soa); } // anonymous namespace @@ -423,15 +424,56 @@ void Csr::move_to(Fbcsr* result) template void Csr::read(const mat_data& data) { - this->read(device_mat_data::create_from_host(this->get_executor(), data)); + auto size = data.size; + auto exec = this->get_executor(); + this->set_size(size); + this->row_ptrs_.resize_and_reset(size[0] + 1); + this->col_idxs_.resize_and_reset(data.nonzeros.size()); + this->values_.resize_and_reset(data.nonzeros.size()); + // the device matrix data contains views on the column indices + // and values array of this matrix, and an owning array for the + // row indices (which doesn't exist in this matrix) + device_mat_data view{exec, size, + array{exec, data.nonzeros.size()}, + this->col_idxs_.as_view(), this->values_.as_view()}; + const auto host_data = + make_array_view(exec->get_master(), data.nonzeros.size(), + const_cast*>( + data.nonzeros.data())); + exec->run( + csr::make_aos_to_soa(*make_temporary_clone(exec, &host_data), view)); + exec->run(csr::make_convert_idxs_to_ptrs(view.get_const_row_idxs(), + view.get_num_stored_elements(), + size[0], this->get_row_ptrs())); + this->make_srow(); } template void Csr::read(const device_mat_data& data) { - // make a copy, read the data in - this->read(device_mat_data{this->get_executor(), data}); + auto size = data.get_size(); + auto exec = this->get_executor(); + this->row_ptrs_.resize_and_reset(size[0] + 1); + this->set_size(size); + // copy the column indices and values array from the device matrix data + // into this. Compared to the read(device_mat_data&&) version, the internal + // arrays keep their current ownership status. + this->values_ = make_const_array_view(data.get_executor(), + data.get_num_stored_elements(), + data.get_const_values()); + this->col_idxs_ = make_const_array_view(data.get_executor(), + data.get_num_stored_elements(), + data.get_const_col_idxs()); + const auto row_idxs = make_const_array_view(data.get_executor(), + data.get_num_stored_elements(), + data.get_const_row_idxs()) + .copy_to_array(); + auto local_row_idxs = make_temporary_clone(exec, &row_idxs); + exec->run(csr::make_convert_idxs_to_ptrs(local_row_idxs->get_const_data(), + local_row_idxs->get_size(), + size[0], this->get_row_ptrs())); + this->make_srow(); } diff --git a/core/test/base/array.cpp b/core/test/base/array.cpp index 56ed407d1e5..fe135ea4f39 100644 --- a/core/test/base/array.cpp +++ b/core/test/base/array.cpp @@ -523,7 +523,7 @@ TYPED_TEST(Array, CopyViewToArray) TYPED_TEST(Array, CopyArrayToView) { TypeParam data[] = {1, 2, 3}; - auto view = gko::make_array_view(this->exec, 3, data); + auto view = gko::make_array_view(this->exec, 2, data); gko::array array_size2(this->exec, {5, 4}); gko::array array_size4(this->exec, {5, 4, 2, 1}); @@ -531,13 +531,49 @@ TYPED_TEST(Array, CopyArrayToView) EXPECT_EQ(data[0], TypeParam{5}); EXPECT_EQ(data[1], TypeParam{4}); - EXPECT_EQ(data[2], TypeParam{3}); - EXPECT_EQ(view.get_size(), 3); + EXPECT_EQ(view.get_size(), 2); EXPECT_EQ(array_size2.get_size(), 2); ASSERT_THROW(view = array_size4, gko::OutOfBoundsError); } +TYPED_TEST(Array, CopyConstViewToArray) +{ + TypeParam data[] = {1, 2, 3, 4}; + auto const_view = gko::make_const_array_view(this->exec, 4, data); + gko::array array(this->exec, {5, 4, 2}); + + array = const_view; + data[1] = 7; + + EXPECT_EQ(array.get_data()[0], TypeParam{1}); + EXPECT_EQ(array.get_data()[1], TypeParam{2}); + EXPECT_EQ(array.get_data()[2], TypeParam{3}); + EXPECT_EQ(array.get_data()[3], TypeParam{4}); + EXPECT_EQ(array.get_size(), 4); + ASSERT_EQ(const_view.get_size(), 4); +} + + +TYPED_TEST(Array, CopyConstViewToView) +{ + TypeParam data1[] = {1, 2, 3, 4}; + TypeParam data2[] = {5, 4, 2}; + auto view = gko::make_array_view(this->exec, 3, data2); + auto const_view3 = gko::make_const_array_view(this->exec, 3, data1); + auto const_view4 = gko::make_const_array_view(this->exec, 4, data1); + + view = const_view3; + data1[1] = 7; + + EXPECT_EQ(view.get_data()[0], TypeParam{1}); + EXPECT_EQ(view.get_data()[1], TypeParam{2}); + EXPECT_EQ(view.get_data()[2], TypeParam{3}); + EXPECT_EQ(view.get_size(), 3); + EXPECT_EQ(const_view3.get_size(), 3); + ASSERT_THROW(view = const_view4, gko::OutOfBoundsError); +} + TYPED_TEST(Array, MoveArrayToArray) { gko::array array(this->exec, {1, 2, 3}); diff --git a/core/test/matrix/coo.cpp b/core/test/matrix/coo.cpp index fb19737be76..44ab2324b72 100644 --- a/core/test/matrix/coo.cpp +++ b/core/test/matrix/coo.cpp @@ -197,6 +197,42 @@ TYPED_TEST(Coo, CanBeReadFromMatrixData) } +TYPED_TEST(Coo, CanBeReadFromMatrixDataIntoViews) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto row_idxs = gko::array(this->exec, 4); + auto col_idxs = gko::array(this->exec, 4); + auto values = gko::array(this->exec, 4); + auto m = Mtx::create(this->exec, gko::dim<2>{2, 3}, values.as_view(), + col_idxs.as_view(), row_idxs.as_view()); + + m->read({{2, 3}, {{0, 0, 1.0}, {0, 1, 3.0}, {0, 2, 2.0}, {1, 1, 5.0}}}); + + this->assert_equal_to_original_mtx(m); + ASSERT_EQ(row_idxs.get_data(), m->get_row_idxs()); + ASSERT_EQ(col_idxs.get_data(), m->get_col_idxs()); + ASSERT_EQ(values.get_data(), m->get_values()); +} + + +TYPED_TEST(Coo, ThrowsOnIncompatibleReadFromMatrixDataIntoViews) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto row_idxs = gko::array(this->exec, 1); + auto col_idxs = gko::array(this->exec, 1); + auto values = gko::array(this->exec, 1); + auto m = Mtx::create(this->exec, gko::dim<2>{2, 3}, values.as_view(), + col_idxs.as_view(), row_idxs.as_view()); + + ASSERT_THROW(m->read({{2, 3}, {{0, 0, 1.0}, {0, 1, 3.0}}}), + gko::NotSupported); +} + + TYPED_TEST(Coo, CanBeReadFromMatrixAssemblyData) { using Mtx = typename TestFixture::Mtx; @@ -215,6 +251,136 @@ TYPED_TEST(Coo, CanBeReadFromMatrixAssemblyData) } +TYPED_TEST(Coo, CanBeReadFromDeviceMatrixData) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto m = Mtx::create(this->exec); + gko::matrix_assembly_data data(gko::dim<2>{2, 3}); + data.set_value(0, 0, 1.0); + data.set_value(0, 1, 3.0); + data.set_value(0, 2, 2.0); + data.set_value(1, 1, 5.0); + auto device_data = + gko::device_matrix_data::create_from_host( + this->exec, data.get_ordered_data()); + + m->read(device_data); + + this->assert_equal_to_original_mtx(m); + ASSERT_EQ(device_data.get_num_stored_elements(), + m->get_num_stored_elements()); + ASSERT_EQ(device_data.get_size(), m->get_size()); +} + + +TYPED_TEST(Coo, CanBeReadFromDeviceMatrixDataIntoViews) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto row_idxs = gko::array(this->exec, 4); + auto col_idxs = gko::array(this->exec, 4); + auto values = gko::array(this->exec, 4); + auto m = Mtx::create(this->exec, gko::dim<2>{2, 3}, values.as_view(), + col_idxs.as_view(), row_idxs.as_view()); + gko::matrix_assembly_data data(gko::dim<2>{2, 3}); + data.set_value(0, 0, 1.0); + data.set_value(0, 1, 3.0); + data.set_value(0, 2, 2.0); + data.set_value(1, 1, 5.0); + auto device_data = + gko::device_matrix_data::create_from_host( + this->exec, data.get_ordered_data()); + + m->read(device_data); + + this->assert_equal_to_original_mtx(m); + ASSERT_EQ(row_idxs.get_data(), m->get_row_idxs()); + ASSERT_EQ(col_idxs.get_data(), m->get_col_idxs()); + ASSERT_EQ(values.get_data(), m->get_values()); +} + + +TYPED_TEST(Coo, ThrowsOnIncompatibleReadFromDeviceMatrixDataIntoViews) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto row_idxs = gko::array(this->exec, 1); + auto col_idxs = gko::array(this->exec, 1); + auto values = gko::array(this->exec, 1); + auto m = Mtx::create(this->exec, gko::dim<2>{2, 3}, values.as_view(), + col_idxs.as_view(), row_idxs.as_view()); + gko::matrix_assembly_data data(gko::dim<2>{2, 3}); + data.set_value(0, 0, 1.0); + data.set_value(0, 1, 3.0); + auto device_data = + gko::device_matrix_data::create_from_host( + this->exec, data.get_ordered_data()); + + ASSERT_THROW(m->read(device_data), gko::OutOfBoundsError); +} + + +TYPED_TEST(Coo, CanBeReadFromMovedDeviceMatrixData) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto m = Mtx::create(this->exec); + gko::matrix_assembly_data data(gko::dim<2>{2, 3}); + data.set_value(0, 0, 1.0); + data.set_value(0, 1, 3.0); + data.set_value(0, 2, 2.0); + data.set_value(1, 1, 5.0); + auto device_data = + gko::device_matrix_data::create_from_host( + this->exec, data.get_ordered_data()); + + m->read(std::move(device_data)); + + this->assert_equal_to_original_mtx(m); + ASSERT_EQ(device_data.get_size(), gko::dim<2>{}); + ASSERT_EQ(device_data.get_num_stored_elements(), 0); +} + + +TYPED_TEST(Coo, CanBeReadFromMovedDeviceMatrixDataIntoViews) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto row_idxs = gko::array(this->exec, 2); + auto col_idxs = gko::array(this->exec, 2); + auto values = gko::array(this->exec, 2); + row_idxs.fill(0); + col_idxs.fill(0); + values.fill(gko::zero()); + auto m = Mtx::create(this->exec, gko::dim<2>{2, 3}, values.as_view(), + col_idxs.as_view(), row_idxs.as_view()); + gko::matrix_assembly_data data(gko::dim<2>{2, 3}); + data.set_value(0, 0, 1.0); + data.set_value(0, 1, 3.0); + data.set_value(0, 2, 2.0); + data.set_value(1, 1, 5.0); + auto device_data = + gko::device_matrix_data::create_from_host( + this->exec, data.get_ordered_data()); + auto orig_row_idxs = device_data.get_row_idxs(); + auto orig_col_idxs = device_data.get_col_idxs(); + auto orig_values = device_data.get_values(); + + m->read(std::move(device_data)); + + this->assert_equal_to_original_mtx(m); + ASSERT_EQ(orig_row_idxs, m->get_row_idxs()); + ASSERT_EQ(orig_col_idxs, m->get_col_idxs()); + ASSERT_EQ(orig_values, m->get_values()); +} + + TYPED_TEST(Coo, GeneratesCorrectMatrixData) { using Mtx = typename TestFixture::Mtx; diff --git a/core/test/matrix/csr.cpp b/core/test/matrix/csr.cpp index b91fc003dc6..7f47ea82bfc 100644 --- a/core/test/matrix/csr.cpp +++ b/core/test/matrix/csr.cpp @@ -8,6 +8,9 @@ #include +#include + + #include "core/test/utils.hpp" @@ -211,6 +214,44 @@ TYPED_TEST(Csr, CanBeReadFromMatrixData) } +TYPED_TEST(Csr, CanBeReadFromMatrixDataIntoViews) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto row_ptrs = gko::array(this->exec, 3); + auto col_idxs = gko::array(this->exec, 4); + auto values = gko::array(this->exec, 4); + auto m = Mtx::create(this->exec, gko::dim<2>{2, 3}, values.as_view(), + col_idxs.as_view(), row_ptrs.as_view(), + std::make_shared(2)); + + m->read({{2, 3}, {{0, 0, 1.0}, {0, 1, 3.0}, {0, 2, 2.0}, {1, 1, 5.0}}}); + + this->assert_equal_to_original_mtx(m); + ASSERT_EQ(row_ptrs.get_data(), m->get_row_ptrs()); + ASSERT_EQ(col_idxs.get_data(), m->get_col_idxs()); + ASSERT_EQ(values.get_data(), m->get_values()); +} + + +TYPED_TEST(Csr, ThrowsOnIncompatibleReadFromMatrixDataIntoViews) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto row_ptrs = gko::array(this->exec, 3); + auto col_idxs = gko::array(this->exec, 1); + auto values = gko::array(this->exec, 1); + auto m = Mtx::create(this->exec, gko::dim<2>{2, 3}, values.as_view(), + col_idxs.as_view(), row_ptrs.as_view(), + std::make_shared(2)); + + ASSERT_THROW(m->read({{2, 3}, {{0, 0, 1.0}, {0, 1, 3.0}}}), + gko::NotSupported); +} + + TYPED_TEST(Csr, CanBeReadFromMatrixAssemblyData) { using Mtx = typename TestFixture::Mtx; @@ -230,6 +271,140 @@ TYPED_TEST(Csr, CanBeReadFromMatrixAssemblyData) } +TYPED_TEST(Csr, CanBeReadFromDeviceMatrixData) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto m = Mtx::create(this->exec, + std::make_shared(2)); + gko::matrix_assembly_data data(gko::dim<2>{2, 3}); + data.set_value(0, 0, 1.0); + data.set_value(0, 1, 3.0); + data.set_value(0, 2, 2.0); + data.set_value(1, 1, 5.0); + auto device_data = + gko::device_matrix_data::create_from_host( + this->exec, data.get_ordered_data()); + + m->read(device_data); + + this->assert_equal_to_original_mtx(m); + ASSERT_EQ(device_data.get_num_stored_elements(), + m->get_num_stored_elements()); + ASSERT_EQ(device_data.get_size(), m->get_size()); +} + + +TYPED_TEST(Csr, CanBeReadFromDeviceMatrixDataIntoViews) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto row_ptrs = gko::array(this->exec, 3); + auto col_idxs = gko::array(this->exec, 4); + auto values = gko::array(this->exec, 4); + auto m = Mtx::create(this->exec, gko::dim<2>{2, 3}, values.as_view(), + col_idxs.as_view(), row_ptrs.as_view(), + std::make_shared(2)); + gko::matrix_assembly_data data(m->get_size()); + data.set_value(0, 0, 1.0); + data.set_value(0, 1, 3.0); + data.set_value(0, 2, 2.0); + data.set_value(1, 1, 5.0); + auto device_data = + gko::device_matrix_data::create_from_host( + this->exec, data.get_ordered_data()); + + m->read(device_data); + + this->assert_equal_to_original_mtx(m); + ASSERT_EQ(row_ptrs.get_data(), m->get_row_ptrs()); + ASSERT_EQ(col_idxs.get_data(), m->get_col_idxs()); + ASSERT_EQ(values.get_data(), m->get_values()); +} + + +TYPED_TEST(Csr, ThrowsOnIncompatibleReadFromDeviceMatrixDataIntoViews) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto row_ptrs = gko::array(this->exec, 3); + auto col_idxs = gko::array(this->exec, 1); + auto values = gko::array(this->exec, 1); + auto m = Mtx::create(this->exec, gko::dim<2>{2, 3}, values.as_view(), + col_idxs.as_view(), row_ptrs.as_view(), + std::make_shared(2)); + gko::matrix_assembly_data data(m->get_size()); + data.set_value(0, 0, 1.0); + data.set_value(0, 1, 3.0); + auto device_data = + gko::device_matrix_data::create_from_host( + this->exec, data.get_ordered_data()); + + ASSERT_THROW(m->read(device_data), gko::OutOfBoundsError); +} + + +TYPED_TEST(Csr, CanBeReadFromMovedDeviceMatrixData) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto m = Mtx::create(this->exec, + std::make_shared(2)); + gko::matrix_assembly_data data(gko::dim<2>{2, 3}); + data.set_value(0, 0, 1.0); + data.set_value(0, 1, 3.0); + data.set_value(0, 2, 2.0); + data.set_value(1, 1, 5.0); + auto device_data = + gko::device_matrix_data::create_from_host( + this->exec, data.get_ordered_data()); + + m->read(std::move(device_data)); + + this->assert_equal_to_original_mtx(m); + ASSERT_EQ(device_data.get_size(), gko::dim<2>{}); + ASSERT_EQ(device_data.get_num_stored_elements(), 0); +} + + +TYPED_TEST(Csr, CanBeReadFromMovedDeviceMatrixDataIntoViews) +{ + using Mtx = typename TestFixture::Mtx; + using value_type = typename TestFixture::value_type; + using index_type = typename TestFixture::index_type; + auto row_ptrs = gko::array(this->exec, 3); + auto col_idxs = gko::array(this->exec, 4); + auto values = gko::array(this->exec, 4); + row_ptrs.fill(0); + col_idxs.fill(0); + values.fill(gko::zero()); + auto m = Mtx::create(this->exec, gko::dim<2>{2, 3}, values.as_view(), + col_idxs.as_view(), row_ptrs.as_view(), + std::make_shared(2)); + gko::matrix_assembly_data data(m->get_size()); + data.set_value(0, 0, 1.0); + data.set_value(0, 1, 3.0); + data.set_value(0, 2, 2.0); + data.set_value(1, 1, 5.0); + auto device_data = + gko::device_matrix_data::create_from_host( + this->exec, data.get_ordered_data()); + auto orig_col_idxs = device_data.get_col_idxs(); + auto orig_values = device_data.get_values(); + + m->read(std::move(device_data)); + + this->assert_equal_to_original_mtx(m); + ASSERT_EQ(row_ptrs.get_data(), m->get_row_ptrs()); + ASSERT_EQ(orig_col_idxs, m->get_col_idxs()); + ASSERT_EQ(orig_values, m->get_values()); +} + + TYPED_TEST(Csr, GeneratesCorrectMatrixData) { using value_type = typename TestFixture::value_type; diff --git a/include/ginkgo/core/base/array.hpp b/include/ginkgo/core/base/array.hpp index 1e9c209bddc..ea5a8e86642 100644 --- a/include/ginkgo/core/base/array.hpp +++ b/include/ginkgo/core/base/array.hpp @@ -550,6 +550,51 @@ class array { return *this; } + /** + * Copies data from a const_array_view. + * + * In the case of an array target, the array is resized to match the + * source's size. In the case of a view target, if the dimensions are not + * compatible a gko::OutOfBoundsError is thrown. + * + * This does not invoke the constructors of the elements, instead they are + * copied as POD types. + * + * The executor of this is preserved. In case this does not have an assigned + * executor, it will inherit the executor of other. + * + * @param other the const_array_view to copy from + * + * @return this + */ + array& operator=(const detail::const_array_view& other) + { + if (this->exec_ == nullptr) { + this->exec_ = other.get_executor(); + this->data_ = data_manager{nullptr, default_deleter{this->exec_}}; + } + if (other.get_executor() == nullptr) { + this->clear(); + return *this; + } + + if (this->is_owning()) { + this->resize_and_reset(other.get_size()); + } else { + GKO_ENSURE_COMPATIBLE_BOUNDS(other.get_size(), this->get_size()); + } + array tmp{this->exec_}; + const ValueType* source = other.get_const_data(); + // if we are on different executors: copy + if (this->exec_ != other.get_executor()) { + tmp = other.copy_to_array(); + source = tmp.get_const_data(); + } + exec_->copy_from(other.get_executor(), other.get_size(), source, + this->get_data()); + return *this; + } + /** * Deallocates all data used by the array. *