保健品网站制作,平台交易网,多后缀域名查询网站,wordpress wpml 下载点蓝色字关注“CurryCoder的程序人生”微信公众号#xff1a;CurryCoder的程序人生欢迎关注我#xff0c;一起学习#xff0c;一起进步!1.问题的引入假如你正在给一个应用写一个矩形类#xff0c;这个矩形由左上角和右下角的顶点坐标表示。为了表示这两个点#xff0c;我们… 点蓝色字关注“CurryCoder的程序人生”微信公众号CurryCoder的程序人生欢迎关注我一起学习一起进步!1.问题的引入假如你正在给一个应用写一个矩形类这个矩形由左上角和右下角的顶点坐标表示。为了表示这两个点我们写一个表示点的类class Point{public: Point(int x, int y); void setX(int newVal); void setY(int newVal); // ....};为了让矩形对象的体积小一点我们将这两个顶点装在另一个结构体中并用指针指向它:struct RectData{ Point ulhc; // 左上角 Point lrhc; // 右下角};class Rectangle{ // ...private: std::shared_ptr pData; };由于用户想要得到点的坐标所以需要让矩形类要提供返回这两个点的函数。因为矩形类是我们自定义的类根据之前的文章C中多用引用传递方式替换值传递方式中提到的对于自定义的类传递引用方式比传值方式更高效所以我们让这两个函数返回引用:class Rectangle{public: // ... Point upperLeft() const { return pData-ulhc; } Point lowerRight() const { return pData-lrhc; }private: std::shared_ptr pData; };上面的代码虽然可以通过编译但却是自我矛盾的首先函数upperLeft()和函数lowerRight()被声明为const成员函数。因为它们的目的是为了只返回一个对象而别的什么都不做但两个函数却都返回了指向私有成员的引用因此调用者就能通过这个引用来改变对象。Point coord1(0, 0);Point coord2(100, 100);const Rectangle rec(coord1, coord2); // 我们希望它是常对象recrec.upperLeft().setX(50); // upperLeft()的调用者rec能够使用被返回的指向rec内部的Point成员变量的引用来更改成员// 但是rec实际上是不可变的因为它是常对象2.得到的结论从上面的代码段中我们可以得到下面两个教训(1).数据成员的最好封装性取决于最能破坏封装的函数。虽然ulhc和lrhc两个点都声明为private但由于存在两个返回引用的函数的存在它们其实相当于是public。因为public函数upperLeft()和lowerLeft()传出了它们的引用。(2).如果一个函数返回了指向储存在对象外部的数据成员的引用即使这个函数声明为了const这个函数的调用者也能修改这个成员。原因见之前的文章尽可能使用const修饰符中的bitwise constness的局限性。除了引用返回指针和迭代器也是相同的结果也是由于相同的原因导致。引用、指针、迭代器都是本文标题中所说的句柄(handle)即接触对象的某种方式。直接返回句柄总会带来破坏封装的风险这也导致声明为const的函数并不是真正的const。注意内部成员除了内部数据还包括内部函数即声明为私有(private)或保护(protected)的函数。因此对于内部函数也是一样也不要返回它们的句柄否则用户也可以通过返回的函数指针来调用它们这样私有的成员函数也相当于变成了公有。3.问题的解决方法回到上面出现自我矛盾的代码段如果要解决返回引用会导致数据成员被改变的问题只需要给函数的返回类型加上一个const。如下面的代码段所示class Rectangle{public: // ... // 现在返回的是const Point const Point upperLeft(){ return pData-ulhc; } const Point lowerRight(){ return pData-lrhc; }private: std::shared_ptr pData; };这样用户就只能对其进行读操作而不能进行写操作了给函数声明的const也就不会骗人了。至于封装性问题让用户知道这个矩形的位置是完全合情合理的所以我们给封装提供了有限的放宽让用户可以读到私有数据但坚决不能让用户执行写操作。然而即使这样返回的句柄仍然会导致一个问题野句柄(dangling handle)即这个句柄指向的对象不存在。最常见的场景是函数返回值假如我们正在给某个GUI对象写一个返回它边界框的函数返回类型是Rectangle。如下面的代码段所示class GUIObject{ // ...};const Rectangle boundingBox(const GUIObject obj);现在客户可能会像下面那样使用这个函数GUIObject* pgo;// ...const Point* pUpperLeft (boundingBox(*pgo).upperLeft());现在有意思的事发生了取址运算符括号里面的函数boundingBox()会返回一个新的临时Rectangle对象称为temp。有了这个临时对象之后我们就可以获得指向它左上角的Point对象然后pUpperLeft自然就获得了这个Point对象的地址。但是temp毕竟是临时对象。在这行代码执行完后temp会被销毁它所包含的Point对象也会被销毁。最后pUpperLeft存储了一个指向不存在的对象的指针。因此这也解释了为什么返回指向内部成员句柄的函数是危险的不管你的句柄是指针、引用还是迭代器不管你的函数返回值是不是const、你的函数是不是const。但是这不代表要杜绝这种做法有时候不得不这样做。例如索引[]操作符用来获取容器(比如std::vector)中的某个对象它返回的是指向容器中的数据的引用来让你完成写操作。记住在我们自己设计的程序中还是尽量避免不要这么做。4.总结(1) 避免返回指向内部成员的句柄(包括指针引用迭代器)。不返回句柄能增强封装性让const函数成为真正的const也能减少野句柄。觉得好看请点这里↓↓↓