C ++ tips: avoid returning the handle of the internal component of the object

Source: Internet
Author: User

Suppose you are working on an application that contains a rectangle. Each rectangle can be expressed in the upper left corner and lower right corner. To keep a Rectangle object in a small State, you may decide that the domain defined by those points should not be included in the Rectangle itself, A more suitable method is to place it in a secondary structure directed by Rectangle:

class Point {
  // class for representing points
  public:
   Point(int x, int y);
   ...

   void setX(int newVal);
   void setY(int newVal);
   ...
};

struct RectData {
  // Point data for a Rectangle
  Point ulhc; // ulhc = " upper left-hand corner"
  Point lrhc; // lrhc = " lower right-hand corner"
};

class Rectangle {
  ...

  private:
   std::tr1::shared_ptr<RectData> pData; // see Item 13 for info on
}; // tr1::shared_ptr

Because Rectangle customers need to be able to manipulate the Rectangle area, the class provides the upperLeft and lowerRight functions. However, Point is a user-defined type. Therefore, in typical cases, it is more efficient to transmit a user-defined type by reference than to transmit a value, these functions return references to the underlying Point object:

class Rectangle {
  public:
   ...
   Point& upperLeft() const { return pData->ulhc; }
   Point& lowerRight() const { return pData->lrhc; }
   ...
};

ThisDesignIt can be compiled, but it is incorrect. In fact, it is self-contradictory. On the one hand, upperLeft and lowerRight are member functions declared as const, because they are designed to only provide customers with a method to obtain the Rectangle, rather than allowing customers to change the Rectangle. On the other hand, both functions return reference to private internal data-the caller can use this reference to modify internal data! For example, Point coord1 (0, 0 );

Point coord2(100, 100);

const Rectangle rec(coord1, coord2); // rec is a const rectangle from
// (0, 0) to (100, 100)

rec.upperLeft().setX(50); // now rec goes from
// (50, 0) to (100, 100)!

Note: Here, how does the upperLeft caller use the reference of the returned rec internal Point data member to change this member. But rec is expected to be const!

This leads to two experiences. First, a data member is encapsulated, but a function with the highest accessible level can still return a reference to it. In the current situation, although ulhc and lrhc are declared as private, they are still publicly available, because the public functions upperLeft and lowerRight return references to them. Second, if a const member function returns a reference, leading to data related to an object and stored outside of the object itself, the caller of this function can change that data (which is a side effect of the limitation of binary constants ).

Everything we did previously involves references returned by member functions. However, if they return pointers or iterators, the same problem also exists for the same reason. References, pointers, and iterators are both handle (Methods for holding other objects), and the handle for returning an object's internal component is always at risk of endangering the object encapsulation security. As we can see, it can also cause the const member function to change the state of an object.

We usually think that the "internal component" of an object is its data member, but it cannot be publicly accessed (that is, it is protected or private) it is also part of the internal component of the object. Similarly, it is important not to return their handles. This means that you should never have a member function to return a pointer to a member function with a small accessible level. If you do this, its accessible level will be the same as that of the function with a large accessible level, because the customer can get a pointer to this function with a small accessible level, then the user can call this function through this pointer.

In any case, it is rare to return a pointer to a member function, so let's focus on the Rectangle class and its upperLeft and lowerRight member functions. All the problems we have picked out in these functions can be ruled out simply by using const for their return types:

class Rectangle {
public:
...
const Point& upperLeft() const { return pData->ulhc; }
const Point& lowerRight() const { return pData->lrhc; }
...
};

With this modified design, the customer can read the Points that define a rectangle, but they cannot write them. This means that declaring upperLeft and upperRight as const is no longer empty talk, because they no longer allow callers to change the object state. As for the encapsulation problem, we always intentionally let the customer see the Points into a Rectangle, so this is a deliberate relaxation of encapsulation. More importantly, it is a limited relaxation: only read access is permitted by these functions, and write access is still disabled.

Even so, upperLeft and lowerRight still return the handle of an object's internal component, which may cause other problems. In particular, this leads to an overhead handle: A handle that references a component of an object that no longer exists. The most common source of this disappearing object is the function return value. For example, consider a function that returns the bounding box of the GUI object in a rectangular form:

class GUIObject { ... };

const Rectangle // returns a rectangle by
boundingBox(const GUIObject& obj); // value; see Item 3 for why
// return type is const

Now, the customer may use this function as follows:

GUIObject *pgo; // make pgo point to
... // some GUIObject

const Point *pUpperLeft = // get a ptr to the upper
&(boundingBox(*pgo).upperLeft()); // left point of its
// bounding box

A new temporary Rectangle object is returned when you call boundingBox. This object has no name, so we call it temp. Therefore, upperLeft is called on temp. This call returns a reference to an internal component leading to temp. In particular, it is composed of Points. Then pUpperLeft points to this Point object. So far, everything is normal, but we cannot continue, because at the end of this statement, the return value of boundingBox -- temp -- is destroyed, this indirectly leads to the analysis structure of the temp Points. Next, the remaining pUpperLeft points to an object that no longer exists; pUpperLeft hangs at the end of the statement for creating it!

This is why any function that returns the handle of an internal component of an object is dangerous. It has nothing to do with whether the handle is a pointer, reference, or iterator. It has nothing to do with whether it is subject to cosnt restrictions. It has nothing to do with whether the handle returned by the member function is const. All of the problems are that a handle is returned, because once this is done, you are faced with the risk that the handle is longer than the referenced object.

This does not mean that you should never let a member function return a handle. Sometimes you must. For example, operator [] allows you to extract individual elements from string and vector, these operator [] s work by returning a reference to the data in the container-when the container itself is destroyed, the data will also be destroyed. However, such a function is a special case rather than a convention.

Things to Remember

· Avoid returning the handle (reference, pointer, or iterator) of the internal component of the object ). This will improve encapsulation, help const member functions produce cosnt effects, and minimize the possibility of generating an empty handle.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.