Show Lecture.ProxyObjects as a slide show.
CS253 Proxy Objects
The problem
- I want a two-dimensional array class.
- It really should support all types, via templates, but we’ll
restrict it to int for pedagogical purposes.
- It should be as similar as possible to a standard C array.
// C style
int foo[2][3];
foo[1][2] = 24;
cout << foo[1][2];
// Using object
TwoD bar(2,3);
bar[1][2] = 24;
cout << bar[1][2];
Start small
Let’s start with a one-dimensional array object,
a thin wrapper around a vector:
class OneD {
vector<int> data;
public:
OneD(int size) : data(size) { }
int& operator[](int i) { return data[i]; }
};
OneD a(42);
a[10] = 33;
a[20] = 55;
cout << a[10]+a[20];
88
- Do you understand the return type of
operator[]
?
- Give me no grief about using vector. It has the same time cost as
doing my own memory management. If I cared about the
space used by vector’s sizes, I’d use
unique_ptr<int[]>
.
First naïve 2D attempt
That worked ok, so let’s add a second argument to operator[]
:
class TwoD {
const int height;
vector<int> data;
public:
TwoD(int w, int h) : height(h), data(w*h) { }
int& operator[](int x, int y) { return data[x*height + y]; } // 🦡
};
c.cc:6: error: ‘int& main()::TwoD::operator[](int, int)’ must have exactly
one argument
Oops—operator[]
takes only one argument.
Regroup
operator[]
takes only a single argument.
- Let’s use round parentheses instead of square brackets.
- Sure, we call
operator()
the “function call operator”,
but it’s just a name.
Second attempt
class TwoD {
const int height;
vector<int> data;
public:
TwoD(int w, int h) : height(h), data(w*h) { }
int& operator()(int x, int y) { return data[x*height + y]; }
};
TwoD a(2,3);
a(1,2) = 24;
cout << a(1,2);
24
Analyze
- That worked, but it’s not what I want.
- I want
a[1][2]
, because that’s what I’m used to, from C++
built-in arrays.
New approach:
class TwoD
will define operator[](int)
, which takes
a single argument, the 1
.
a[1]
will return a proxy object. What type? Who cares!
- The proxy object will also have
operator[]
defined,
which will handle the [2]
.
- proxyobject
[2]
returns a reference to an int.
- So,
a[1][2]
is equivalent to (a[1])[2]
,
or a.operator[](1).operator[](2)
.
Use a proxy class
struct Proxy {
int *const dp;
Proxy(int *dataptr) : dp(dataptr) { }
int& operator[](int y) { return dp[y]; }
};
class TwoD {
const int height;
vector<int> data;
public:
TwoD(int w, int h) : height(h), data(w*h) { }
Proxy operator[](int x) { return &data[x*height]; }
};
TwoD a(2,3);
a[1][2] = 24;
cout << a[1][2];
24
- “struct”⁇ Sure, everything’s public, and this is all internal.
- What is
TwoD::operator[]
’s return type? Did we return that?
Adjustment
- That’s fine, but we’re polluting the global namespace.
- The user expects the
TwoD
class, but not the Proxy
class.
- The user might have their own
Proxy
class or type.
- The safest thing to do is to put
Proxy
inside TwoD
.
- Put it in the
private:
section, because the user doesn’t
need to access it explicitly.
With a nested class
class TwoD {
struct Proxy {
int *const dp;
Proxy(int *dataptr) : dp(dataptr) { }
int& operator[](int y) { return dp[y]; }
};
const int height;
vector<int> data;
public:
TwoD(int w, int h) : height(h), data(w*h) { }
Proxy operator[](int x) { return &data[x*height]; }
};
TwoD a(2,3);
a[1][2] = 24;
cout << a[1][2];
24