读书笔记《Effective C++》条款28:避免返回handles指向对象内部成分

假设一个程序设计矩形。每个矩形由其左上角和右下角表示。为了让一个Rectangle对象尽可能小,可能会决定不把定义矩形的这些点存放在Rectangle对象内,而是放在一个struct中,再让Rectangle指向它。Rectangle的客户必须能够计算Rectangle的范围,所以这个class提供upperLeft函数和lowerRight函数,这两个函数返回是个reference,代表底层的Point对象。

class Point {//这个class用来表述“点”
public:
	Point() : x(0), y(0) {
	
	}
	Point(int x, int y) : x(x), y(y) {


	}


	void setX(int x) {
		this->x = x;
	}
	void setY(int y) {
		this->y = y;
	}
	int getX() {
		return x;
	}
	int getY() {
		return y;
	}
private:
	int x;
	int y;
};


struct RectData {//这些Point数据用来表现一个矩形
	Point ulhc;//upper left-hand corner(左上角)
	Point lrhc;//lower right-hand corner(右下角)
};


class Rectangle {
public:
	Rectangle(Point& ulhc, Point& lrhc) {
		std::shared_ptr<RectData> p(new RectData);
		pData = p;
		pData->ulhc = ulhc;
		pData->lrhc = lrhc;
	}
	Point& upperLeft() const {
		return pData->ulhc;
	}
	Point& lowerRight() const {
		return pData->lrhc;
	}
private:
	std::shared_ptr<RectData> pData;
};
这样的设计可以通过编译,但实际上是自我矛盾的,一方面upperLeft和lowerRight被声明为const成员函数,因为它们的目的只是为了提供客户一个得知Rectangle相关坐标点的方法,而不是让客户修改Rectangle。但是这两个成员函数的返回值却是reference指向private内部数据,调用者于是可通过这些reference更改内部数据!例如:

Point coord1(0, 0);
Point coord2(100, 100);
const Rectangle rec(coord1, coord2);//rec是个const矩形,从(0, 0)到(100, 100)
std::cout << "rectangle ("
	<< "ulhc.x: " << rec.upperLeft().getX()
	<< " ulhc.y: " << rec.upperLeft().getY()
	<< " lrhc.x: " << rec.lowerRight().getX()
	<< " lrhc.y: " << rec.lowerRight().getY()
	<< ")"
	<< std::endl;
rec.upperLeft().setX(50);//现在rec却变成从(50, 0)到(100, 100)
std::cout << "rectangle ("
	<< "ulhc.x: " << rec.upperLeft().getX()
	<< " ulhc.y: " << rec.upperLeft().getY()
	<< " lrhc.x: " << rec.lowerRight().getX()
	<< " lrhc.y: " << rec.lowerRight().getY()
	<< ")"
	<< std::endl;
我们看看VS2013执行结果:


从执行结果来看,private内部数据ulhc.x的值被改变了。

upperLeft的调用者能够使用被返回的reference(指向rec内部的Point成员变量)来更改private内部成员。但rec其实应该是不可改变的(const)!

这给我们两个教训。第一,成员变量的封装性最多只等于“返回其reference”的函数的访问级别。本例中虽然ulhc和lrhc都被声明为private,它们实际上却是public,因为public函数upperLeft和lowerRight传出了它们的reference。第二,如果const成员函数传出一个reference,后者所指数据与对象自身关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据。

上面所说的每件事情都是由于“成员函数返回reference”。如果它们返回的是指针或者迭代器,相同的情况还是会发生,原因相同。Reference、指针和迭代器统统都是所谓的handles,而返回一个“代表对象内部数据”的handles,随之而来的便是“降低对象封装性”的风险。同时,它也可能导致“虽然调用const成员函数却造成对象状态被更改”。

这意味着绝对不该令成员函数返回一个指针指向“访问级别较低”的成员函数。

那如何处理这个问题呢?我们只要对它们的返回类型加上const即可:

class Rectangle {
public:
	Rectangle(Point& ulhc, Point& lrhc) {
		std::shared_ptr<RectData> p(new RectData);
		pData = p;
		pData->ulhc = ulhc;
		pData->lrhc = lrhc;
	}
	const Point& upperLeft() const {
		return pData->ulhc;
	}
	const Point& lowerRight() const {
		return pData->lrhc;
	}
private:
	std::shared_ptr<RectData> pData;
};
有了这样的修改,客户客户读取矩形的Points,但不能修改它们。

但即便如此,有可能在其他场合带来问题。比如,它可能导致空悬的handles(如指针):这种handles所指东西不复存在。

这也是为什么函数如果“返回一个handle代表对象内部成分”总是危险的原因。不论这个所谓的handle是指针还是reference,也不论这个handle是否为const,也不论那个返回handle的成员函数是否为const。这里的唯一关键是,有个handle被传出去了,一旦如此你就是暴露在“handle比其所指对象更长寿”的风险下。


要点:

避免返回handles(包括reference、指针、迭代器)指向对象内部。遵守这个条款可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”(dangling handles)的可能性将至最低。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值