首先拋出結論:
- 不要返回對象私有成員的 handles ,handles 指引用,指針和迭代器。這樣可以增強類的封裝性,避免可能出現的
const
成員函數不const
(即外部仍然可以通過const
成員函數修改對象內部私有數據),以及 dangling handles 的問題 (即空指針或空引用)。
返回私有成員指針
在下面 LeftBottom
和 RightTop
函數中,我們是想要客戶端獲得 Rectangle
對象的左下角和右上角的點座標。因爲在這兩個函數內部,並沒有修改對象內部數據,所以將這兩個函數設爲 const
成員函數。又爲了提高數據傳遞的效率,我們直接以 by reference 的方式傳遞數據:
class Point {
private:
int m_x, m_y;
public:
Point(int x, int y):m_x(x), m_y(y) { }
void SetX(int x) { m_x = x; }
void SetY(int y) { m_y = y; }
//...
};
class Rectangle
{
public:
//...
Point& LeftBottom() const { return m_leftBottom; }
Point& RightTop() const { return m_rightTop; }
private:
Point m_leftBottom, m_rightTop;
};
這個時候,編譯器就報錯了:
將 “Point &” 類型的引用綁定到 “const Point” 類型的初始值設定項時,限定符被丟棄。
這是因爲,我們將這兩個成員函數設定爲了 const
,也就是說,是不希望在這個成員函數中修改對象的內部數據的。但是,由於我們返回了一個 reference
,客戶端在使用時,仍然可以通過這個 reference
修改對象的內部數據,比如說像這樣:
Rectangle rec;
Point& p = rec.LeftBottom();
p.SetX(5);
你可能會想到,那就把返回類型設置爲 const Point&
嘛,這樣客戶端就不能夠修改數據了。的確,但是問題卻並沒有到此爲止。c++ 的常量定義爲 “bitwise constness”,也就是說,只要當前對象沒有被修改就算常量。所以,如果我們將 m_leftBottom
和 m_rightTop
數據成員放在外部,也就是說用一個指針指向這兩個數據成員,這個時候,常量方法的返回類型檢查就會失效:
struct RectData
{
Point leftBottom;
Point rightTop;
RectData(const Point& p1, const Point& p2)
{
leftBottom = p1;
rightTop = p2;
}
};
class Rectangle
{
public:
//...
Rectangle(RectData *p):pData(p) { }
Point& LeftBottom() const { return pData->leftBottom; }
Point& RightTop() const { return pData->rightTop; }
private:
RectData *pData;
};
這個時候,pData->leftBottom
和 pData->rightTop
並不在這個對象的內存裏,因此常量成員函數的返回值就可以不聲明爲 const
,編譯通過。客戶端仍然可以通過返回的 reference
修改對象內部的值:
Point p1, p2;
RectData data(p1, p1);
Rectangle rec(&data);
Point& p = rec.LeftBottom();
p.SetX(5);
dangling handles 問題
對於上面所提及的問題,解決方法也正如一開始說的那樣,我們手動限制返回類型爲 const
,限制客戶端修改代碼:
const Point& LeftBottom() const { return pData->leftBottom; }
很多情況下我們也的確是這樣解決的。比如,在實現 operator [] () const
時。但是這只是一個特例,通常情況下,我們不應該返回對象內部的數據指針,因爲返回的指針,和擁有它的對象,具有相同的生命週期,被返回的指針容易變成 dangling handles。比如在下面這種情況下:
class GUIObject { ... };
const Rectangle BoundingBox(const GUIObject& obj) { ... }
GUIObject obj;
Point& p = BoundingBox(obj).LeftBottom();
p 指向的是 BoundingBox(obj)
返回的臨時對象的內部數據引用,一旦這個函數調用結束,這個臨時對象就會被析構,p 就成爲了懸掛引用,帶來問題。