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:
class OneD {
const int size;
int *const data;
public:
OneD(int size) : size(size), data(new int[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 difference between
size
and size
?
- whether
const int size
is public or private?
- the difference between the two uses of const?
- the return type of
operator[]
?
First naïve 2D attempt
That worked ok, so let’s add a second argument to operator[]
:
class TwoD {
const int height;
int *const data;
public:
TwoD(int w, int h) : height(h), data(new int[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;
int *const data;
public:
TwoD(int w, int h) : height(h), data(new int[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:
a[1]
should return a proxy object. What type? Who cares!
- proxyobject
[2]
returns a reference to an int.
- So,
a[1][2]
is equivalent to (a[1])[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;
int *const data;
public:
TwoD(int w, int h) : height(h), data(new int[w*h]) { }
Proxy operator[](int x) { return &data[x*height]; }
};
TwoD a(2,3);
a[1][2] = 24;
cout << a[1][2];
24
- “struct”⁇
- 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;
int *const data;
public:
TwoD(int w, int h) : height(h), data(new int[w*h]) { }
Proxy operator[](int x) { return &data[x*height]; }
};
TwoD a(2,3);
a[1][2] = 24;
cout << a[1][2];
24
Poor memory management
We’ve been quite lax with regard to memory management.
- Nobody’s freeing the dynamically-allocated memory.
- The default copy ctor is doing a shallow copy.
- The default assignment operator is doing a shallow copy.
I don’t feel like implementing a deep copy, so let’s just forbid copying.
Better memory management
class TwoD {
struct Proxy {
int *const dp;
Proxy(int *dataptr) : dp(dataptr) { }
int &operator[](int y) { return dp[y]; }
};
const int height;
int *const data;
public:
TwoD(int w, int h) : height(h), data(new int[w*h]) { }
TwoD(const TwoD &) = delete;
TwoD &operator=(const TwoD &) = delete;
~TwoD() { delete[] data; }
Proxy operator[](int x) { return &data[x*height]; }
};
TwoD a(2,3);
a[1][2] = 24;
cout << a[1][2];
24
Moral
- The
Proxy
object was just for the benefit of the implementation.
- The user has no interest in
Proxy
objects.
- Don’t be afraid of creating a class for your own purposes.
- But don’t pollute the user’s namespace.
- If your attitude is “Writing classes is hard; I will never write a
class that isn’t required”, then you’ve doomed yourself to mediocrity.
You will forever be a function-oriented programmer trying to live in an
object-oriented world.
- Classes are the basic building blocks of object-oriented programming.
- Get used to them.