Effective C++ item 28: Avoid Returning "handles" to Object Internals

Assume you have following classes

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
class Point {
public:
Point()
: m_x(0)
, m_y(0)
{}
Point(int x, int y)
: m_x(x)
, m_y(y)
{}
void setX(int newVal)
{
m_x = newVal;
}
void setY(int newVal)
{
m_y = newVal;
}
std::ostream& print(std::ostream& out) const
{
out << "x: " << m_x << ", y: " << m_y;
return out;
}

private:
int m_x;
int m_y;
};

std::ostream& operator<<(std::ostream& out, const Point& val)
{
val.print(out);
return out;
}

struct RectData {
Point ulhc;
Point lrhc;
};

class Rectangle {
public:
Rectangle(const Point& coord1, const Point& coord2)
: m_data(new RectData())
{
m_data->ulhc = coord1;
m_data->lrhc = coord2;
}
Point& upperLeft() const { return m_data->ulhc; }
Point& lowerRight() const { return m_data->lrhc; }
private:
std::shared_ptr<RectData> m_data;
};

Can you spot what’s wrong in the code? It will compile but it’s wrong. The problem is Rectangle object returns a handle of internal Point representation. Although upperLeft and lowerRight are const member function, caller can actually modify points returned by these handles.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
Point a(0, 2);
Point b(1, 5);
Rectangle rec(a, b);
std::cout << a << std::endl;
std::cout << b << std::endl;
std::cout << rec.upperLeft() << std::endl;
std::cout << rec.lowerRight() << std::endl;
rec.upperLeft().setX(99);
rec.lowerRight().setY(-10);
std::cout << rec.upperLeft() << std::endl;
std::cout << rec.lowerRight() << std::endl;
return 0;
}

Above code will print

1
2
3
4
5
6
x: 0, y: 2
x: 1, y: 5
x: 0, y: 2
x: 1, y: 5
x: 99, y: 2
x: 1, y: -10

Another example is if you return an internal pointer to caller, it may outlive the life of your object, and the pointer will become dangling pointer.

Rule of thumb is to avoid returning handles (references, pointers, or iterators) to object internals. Doing so will increases encapsulation, helps const member functions act const, and minimizes the creation of dangling handles.

Reference:
“Effective C++” Third Edition by Scott Meyers.