Chester VonWinchester Chester VonWinchester - 1 month ago 7
C++ Question

Writing an accessor method for inherited class with sparse member data?

Say I have a simple vector class,

vec
:

#include <iostream>
#include <stdlib.h>

class vec {
public:
vec() {}
// Constructor.
vec(int n) {
len = n;
data = new double[len];
}

// Destructor.
~vec() { delete [] data; }

// Accessor.
double & operator[](int i) const {
check_index(i);
return data[i];
}

// Other methods...
// ....

protected:
int len;
double * data;
void check_index(int i) const {
if(i < 0 || i >= len) {
std::cerr << "Bad access.\n";
exit(1);
}
}
};


Now suppose I have a special type of
vector
with sparse structure, e.g., where every even-index is zero. Call this
oddvec
. Instances of
oddvec
should be declared just as with the
vec
class, but underneath, the memory use should be efficient since only half the data is non-zero.

The accessor for the
oddvec
class should return 0 if the index is even, and return the odd-index element (stored sequentially) otherwise. There a couple problems with this:


  1. The
    double &
    return type is violated if the index is even, since the constant value,
    0
    , is returned.

  2. It's not clear to me how to handle the situation when an even index element is used as an
    lvalue
    . E.g.,
    v[0] = 3.0
    should not be allowed in the
    oddvec
    class, but is perfectly acceptable in the
    vector
    class. We can't simply throw an error when even indexes are used, because even indexes are fine as long as the intention is as an
    rvalue
    .



How do I design the accessor function for the
oddvec
class, while both keeping the memory storage efficient and inheriting all the methods from the parent?

Non-working example of
oddvec
:

class oddvec : public vec {
public:
// Constructor.
oddvec(int n) {
len = n;
data = new double[len/2];
}

// Accessor (doesn't work!)
double & operator[](int i) const {
check_index(i);

if (i%2 == 0)
return 0;
else
return data[(i-1)/2];
}
};


Upon compilation:

main.cpp: In member function ‘double& oddvec::operator[](int) const’:
main.cpp:49:20: error: invalid initialization of non-const reference of type ‘double&’ from an rvalue of type ‘double’
return 0;





Working example using proxy classes:



I have implemented a proxy class as suggested in the answer below.


proxies.h


#ifndef PROXIES_H
#define PROXIES_H

#include <iostream>
#include <stdlib.h>

class proxy {
public:
proxy(int i, double v, double * d) {
index = i;
value = v;
data = d;
}
void operator=(double rhs) {
data[index] = rhs;
}
friend std::ostream & operator<<(std::ostream & outs, const proxy & p) {
outs << p.value;
return outs;
}
protected:
int index;
double value;
double * data;
};

class oddproxy : public proxy {
public:
oddproxy(int i, int v, double * d) : proxy(i, v, d) {}
void operator=(double rhs) {
if (index%2 == 0) {
std::cerr << "Even entries of oddvec are not assignable.\n";
exit(1);
}
data[index/2] = rhs;
}
};

#endif



vectors.h


#ifndef VECTORS_H
#define VECTORS_H

#include "proxies.h"

class vec {
public:
vec() {}
// Constructor.
vec(int n) {
len = n;
data = new double[len];
}

// Destructor.
~vec() { delete [] data; }

// Accessor.
proxy operator[](int i) const {
check_index(i);
return proxy(i, data[i], data);
}

inline int length() const { return len; }

// Other methods...
// ....

protected:
int len;
double * data;
void check_index(int i) const {
if(i < 0 || i >= len) {
std::cerr << "Bad access.\n";
exit(1);
}
}
};


class oddvec : public vec {
public:
// Constructor.
oddvec(int n) {
len = n;
data = new double[len/2];
}

// Accessor.
oddproxy operator[](int i) const {
check_index(i);
return oddproxy(i, (i%2 == 0) ? 0 : data[i/2], data);
}
};

#endif



main.cpp


#include <iostream>
#include "vectors.h"

int main () {
int N = 5;
vec V(N);
oddvec O(N);

for(int i=0; i < V.length(); i++) {
V[i] = i;

if(i%2 != 0) {
O[i] = i;
}
}

for(int i=0; i < O.length(); i++) {
std::cout << "V[" << i << "]=" << V[i] << ", "
<< "O[" << i << "]=" << O[i] << "\n";
}

O[0] = 13;

return 0;
}



output


V[0]=0, O[0]=0
V[1]=1, O[1]=1
V[2]=2, O[2]=0
V[3]=3, O[3]=3
V[4]=4, O[4]=0
Even entries of oddvec are not assignable.

Answer

You can use proxy object to do this.


simple sample code:

#include <iostream>
#include <vector>
using namespace std;

class very_odd_vector{
public:
    class only_odd_proxy;
    friend class only_odd_proxy;
    only_odd_proxy operator [](int index);
    int operator [](int index)const{return index%2==0?0:content[index/2];}
    unsigned int size()const{return content.size()*2;}
private:
    vector<int> content{1,3,5,7,9};
};

class very_odd_vector::only_odd_proxy{
public:
   only_odd_proxy(very_odd_vector& vec,int index):vec(vec),index(index){}
   operator int(){return index%2==0 ? 0 : vec.content[index/2];}
   only_odd_proxy& operator =(int value){
      if(index%2==0)
         cout << "BAD OPERATION";//any error you want
      else
         vec.content[index/2] = value;
      return *this;
   }
private:
   very_odd_vector& vec;
   int index;
};

auto very_odd_vector::operator [](int index)->only_odd_proxy{return only_odd_proxy(*this,index);}

int main(){
   very_odd_vector v;

   cout << "reading value\n";
   for(int i=0;i<v.size();++i)
      cout << v[i] <<'\n';

   cout << "writting value\n";
   for(int i=0;i<v.size();++i){
      cout << i << ':';
      v[i]=10;
      cout << '\n';
   }

   cout << "reading value\n";
   for(int i=0;i<v.size();++i)
      cout << v[i] <<'\n';
}