【Effective C++】避免返回 handles 指向對象內部成分

首先拋出結論:

  • 不要返回對象私有成員的 handles ,handles 指引用,指針和迭代器。這樣可以增強類的封裝性,避免可能出現的 const 成員函數不 const (即外部仍然可以通過 const 成員函數修改對象內部私有數據),以及 dangling handles 的問題 (即空指針或空引用)。

返回私有成員指針

在下面 LeftBottomRightTop 函數中,我們是想要客戶端獲得 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_leftBottomm_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->leftBottompData->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 就成爲了懸掛引用,帶來問題。

參考

不要返回對象內部的句柄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章