Reputation: 1407
Can a c++ object have a method that return a reference to itself? I want multiple independent instance of the class of the object.
It is for implementing the []
operator of a multi-dimensional array.
I want something like Array[0][1][2]
to work.
Thanks.
Update:
Implementation of a multi-dimensional array:
Intended usage:
A[0][1] = 4; //the element at 0th row and 1st column is now 4.
A[0] = 5; //all elements of the 0th row are now 5.
A["all"][1] = 10; //all elements of the 1st column are now 10;
Object A has pointer ptr_buffer
to a chunk of memory with correct simd alignment. Constructor of A would allocate the memory. Destructor of A deallocate the memory. A[] returns object B.
Object B has a pointer ptr_A_buffer
to a subsection of the memory of A. B[] modifies ptr_A_buffer
and also returns reference to itself. I don't want to constantly make a bunch of object B for each [] operation.
Both A and B belong to the same abstract class. Math functions take the abstract class as argument.
Upvotes: 0
Views: 1788
Reputation: 16017
I was curious about the idea. Here is a data type which seems to fit the bill. It's an array type which, when called with three integers for the dimensions, allocates contiguous memory and then allows the user to define "views" into the data which have smaller dimensions (a plane, a row, a single value).
I made use of shared pointers which I have not really used before, so I may have made mistakes. I welcome corrections.
The idea is that copying views around is shallow; they all operate on the same underlying data. This allows me to pass them around by value with, as I believe, reasonable efficiency.
#include <iostream>
#include <iomanip>
#include <memory>
using namespace std;
/// This class is a three-dimensional array of doubles.
/// It defines an index operator which returns a view into that
/// data that is of one lesser dimension, just like the standard
/// index operator on plain old arrays. The last index operation
/// yields an "array" which is a single value.
/// Converting to double and assigning from double is defined as
/// using the first double in the view.
class NDimArrT
{
/// All slices hold a shared pointer to the base data they
/// are looking into so that their view stays valid.
const shared_ptr<double> baseData;
/// The data the view is looking at. Identical to the shared
/// ptr for the original object.
double *const slice;
/// The three dimensions, in the order of indexing.
/// All three of them may be zero, indicating a single value.
const int dim1, dim2, dim3;
public:
/// A single double value view into a one-dimensional array
NDimArrT(const shared_ptr<double> base, double *sliceStart)
: baseData(base), slice(sliceStart), dim1(0), dim2(0), dim3(0) {}
/// A 1D vector/row view into a two-dimensional array.
/// @param dim1Arg is the length of the row.
NDimArrT(const shared_ptr<double> base, double *sliceStart, int dim1Arg)
: baseData(base), slice(sliceStart), dim1(dim1Arg), dim2(0), dim3(0) {}
/// A 2D matrix plane view into the cube
NDimArrT(const shared_ptr<double> base, double *sliceStart, int dim1Arg, int dim2Arg)
: baseData(base), slice(sliceStart), dim1(dim1Arg), dim2(dim2Arg), dim3(0) {}
/// A 3D cube. This actually allocates memory.
NDimArrT(int dim1Arg, int dim2Arg, int dim3Arg)
: baseData(new double[dim1Arg*dim2Arg*dim3Arg], std::default_delete<double[]>() ),
slice(baseData.get()), // the data view is the whole array
dim1(dim1Arg), dim2(dim2Arg), dim3(dim3Arg) {}
/// Perform a shallow copy. We assume that e.g. returning a slice means
/// essentially returning another view into the main base array.
NDimArrT(const NDimArrT &rhs) = default;
/// Use standard move semantics. The rhs will be invalidated, and the
/// reference count to the base array does not grow. Can be used to return results from
/// functions.
NDimArrT(NDimArrT &&rhs) = default;
/// Default destructor; destroy baseData if it's the last reference.
~NDimArrT() = default;
/// Get the value of the first element we look at. Handy for
/// single value views.
operator double() const { return *slice; }
/// Return an instance of NDimArrT representing a view
/// with one dimension less than this. If we have a single value
/// already, simply return this single value. (We should
/// perhaps throw an exception there.)
NDimArrT operator[](int ind)
{
// This could be regarded an error, because this view
// is already a single element.
if(GetDims() == 0) { return *this; }
// This view is a 1-dimensional vector. Return the element at ind.
if(dim2==0) { return NDimArrT(baseData, slice + ind); } // return a single value.
// This view is a 2D matrix. Return the row no. ind.
// The row length is dim2. (Dim 1 indicates how many rows.)
if(dim3==0) { return NDimArrT(baseData, slice + dim2*ind, dim2); } // return a 1D vector
// This view is a true 3D cube matrix. dim1 is the number of planes,
// dim2 is the number of rows in a plane, dim3 is the length of each row.
// Return the plane no. ind, which starts at ind*planesize, i.e. ind*dim2*dim3.
// The number of rows and row length are dim2 and dim3, respectively.
return NDimArrT(baseData, slice + dim2*dim3*ind, dim2, dim3); // return a 2D matrix.
}
NDimArrT &operator=(double d) { *slice = d; }
int Len() { return dim1 ? dim1 : 1; } // interestingly, length is always dim1.
int GetDims() const
{
return dim1
? dim2
? dim3
? 3
: 2
: 1
: 0;
}
};
/// An example function which initializes an NDimArr of unknown
/// dimensionality with nice numbers..
void initNDimArr(NDimArrT arr, int &startVal)
{
// Single value? Give it the start value and increment that.
if( arr.GetDims() == 0 ) { arr = startVal++; }
else
{
for(int ind=0; ind<arr.Len(); ind++) { initNDimArr(arr[ind], startVal); }
}
}
// An example function doing something with
// an unknown n-dimensional array
void printNdimArr( NDimArrT nDimArr)
{
if( nDimArr.GetDims() == 0) { cout << setw(4) << nDimArr << " "; }
else
{
for(int i=0; i<nDimArr.Len(); i++) { printNdimArr(nDimArr[i]); }
cout << endl;
}
}
int main()
{
NDimArrT arr(3,4,5);
int start = 1;
initNDimArr(arr, start);
printNdimArr(arr);
// now use the middle plane of the 3 4x5 planes
cout << "Middle plane, values starting at 100:" << endl;
auto middlePlane = arr[1];
start = 100;
initNDimArr(middlePlane, start);
printNdimArr(middlePlane);
cout << "Whole array now:" << endl;
printNdimArr(arr);
cout << "Print line 2 of the 3rd plane:" << endl;
printNdimArr(arr[2][1]);
cout << endl << "Last number in that row is " << arr[2][1][4] << endl;
}
Sample session:
$>g++ -std=c++11 -o ndimContiguousArr ndimContiguousArr.cpp && ./ndimContiguousArr
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
21 22 23 24 25
26 27 28 29 30
31 32 33 34 35
36 37 38 39 40
41 42 43 44 45
46 47 48 49 50
51 52 53 54 55
56 57 58 59 60
Middle plane, values starting at 100:
100 101 102 103 104
105 106 107 108 109
110 111 112 113 114
115 116 117 118 119
Whole array now:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
100 101 102 103 104
105 106 107 108 109
110 111 112 113 114
115 116 117 118 119
41 42 43 44 45
46 47 48 49 50
51 52 53 54 55
56 57 58 59 60
Print line 2 of the 3rd plane:
46 47 48 49 50
Last number in that row is 50
Upvotes: 1