📄 ch03.htm
字号:
<h2> <a name="Heading17"> Returning Objects by Value</a></h2><p>For the sake of efficiency, large objects are usually passed to -- or returned from -- a function by reference or by their address. There are, however, a few circumstances in which the best choice is still to return an object by value. Operator <tt>+</tt> is an example of this situation. It has to return a result object, but it cannot modify any of its operands. The seemingly natural choice is to allocate the result object on the free store and return its address. Nevertheless, this is not such a good idea. Dynamic memory allocation is significantly slower than local storage. It also might fail and throw an exception, which then has to be caught and handled. Even worse, this solution is error prone because it is unclear who is responsible for deleting this object -- the creator or the user?</p><p>Another solution is to use a static object and return it by reference. For example</p><pre><tt>class Year</tt><tt>{</tt><tt>private:</tt><tt> int year;</tt><tt>public:</tt><tt> Year(int y = 0) : year(y) {}</tt><tt> Year& operator + (const Year& other) const; //returns a reference to </tt><tt> //a local static Year</tt><tt> int getYear() const;</tt><tt> void setYear(int y);</tt><tt>};</tt><tt>Year& Year::operator + (const Year& other) const </tt><tt>{ </tt><tt> static Year result;</tt><tt> result = Year(this->getYear() + other.getYear() ); </tt><tt> return result;</tt><tt>}</tt></pre><p>Static objects solve the ownership problem, but they are still problematic: On each invocation of the overloaded operator, the same instance of the static object is being modified and returned to the caller. The same object can be returned by reference to several more users, who do not know that they are holding a shared instance that has just been modified behind their back.</p><p>Finally, the safest and most efficient solution is still to return the result object by value:</p><pre><tt>class Year</tt><tt>{</tt><tt>private:</tt><tt> int year;</tt><tt>public:</tt><tt> Year(int y = 0) : year(y) {}</tt><tt> Year operator + (const Year& other) const; //return Year object by value</tt><tt> int getYear() const;</tt><tt> void setYear(int y);</tt><tt>};</tt><tt>Year Year::operator + (const Year& other) const</tt><tt>{ </tt><tt> return Year(this->getYear() + other.getYear() ); </tt><tt>}</tt></pre><h2> <a name="Heading18">Multiple Overloading</a></h2><p>Overloaded operators obey the rules of function overloading. Therefore, it is possible to overload an operator more than once. When is it useful? Consider the following <tt>Month</tt> class and its associated operator<tt> ==</tt>:</p><pre><tt>class Month</tt><tt>{</tt><tt>private:</tt><tt> int m;</tt><tt>public:</tt><tt> Month(int m = 0); </tt><tt>};</tt><tt>bool operator == (const Month& m1, const Month &m2);</tt></pre><p>It is possible to use the overloaded operator <tt>==</tt> to compare a plain <tt>int</tt> value and a <tt>Month</tt> object due to the implicit conversion of <tt>int</tt> to <tt>Month</tt>. For example</p><pre><tt>void f()</tt><tt>{</tt><tt> int n = 7;</tt><tt> Month June(6);</tt><tt> bool same = </tt><tt> (June == n); //calls bool operator == (const Month& m1, const Month &m2);</tt><tt>}</tt></pre><p>This works fine, but it i's inefficient: The argument <tt>n</tt> is first converted to a temporary <tt>Month</tt> object, which is then compared with the object <tt>June</tt>. You can avoid the unnecessary construction of a temporary object by defining additional overloaded versions of operator <tt>==</tt>:</p><pre><tt>bool operator == (int m, const Month& month);</tt><tt>bool operator == (const Month& month, int m);</tt></pre><p>Consequently, the expression <tt>June == n</tt> will now invoke the following overloaded operator:</p><pre><tt>bool operator == (const Month& month, int m);</tt></pre><p>This overloaded version does not create a temporary object, so it's more efficient. The same performance considerations led the C++ Standardization committee to define three distinct versions of operator <tt>==</tt> for <tt>std::string</tt> (see Chapter 10, ""STL and Generic Programming"") and other classes of the Standard Library.' </p><h2> <a name="Heading19">Overloading Operators for Other User-Defined types</a></h2><p>You can overload an operator for <tt>enum</tt> types as well. For example, it may be useful to overload operators such as ++ and <tt>--</tt> so that they can iterate through the enumerator values of a given <tt>enum</tt> type. You can do it like this:</p><pre><tt>#include <iostream></tt><tt>using namespace std;</tt><tt>enum Days </tt><tt>{</tt><tt> Monday, </tt><tt> Tuesday,</tt><tt> Wednesday, </tt><tt> Thursday, </tt><tt> Friday, </tt><tt> Saturday, </tt><tt> Sunday</tt><tt>};</tt><tt>Days& operator++(Days& d, int) // postfix ++</tt><tt>{</tt><tt> if (d == Sunday) </tt><tt> return d = Monday; //rollover</tt><tt> int temp = d; //convert to an int</tt><tt> return d = static_cast<Days> (++temp); </tt><tt>}</tt><tt>int main()</tt><tt>{</tt><tt> Days day = Monday;</tt><tt> for (;;) //display days as integers</tt><tt> {</tt><tt> cout<< day <<endl;</tt><tt> day++;</tt><tt> if (day == Sunday) </tt><tt> break;</tt><tt> }</tt><tt> return 0;</tt><tt>}</tt></pre><p>If you prefer to view the enumerators in their symbolic representation rather than as integers, you can overload the operator <tt><<</tt> as well:</p><pre><tt>ostream& operator<<(ostream& os, Days d) //display Days in symbolic form</tt><tt>{</tt><tt> switch</tt><tt> {</tt><tt> case Monday:</tt><tt> return os<<"Monday";</tt><tt> case Tuesday:</tt><tt> return os<<"Tuesday";</tt><tt> case Wednesday:</tt><tt> return os<<"Wednesday";</tt><tt> case Thursday:</tt><tt> return os<<"Thursady";</tt><tt> case Friday:</tt><tt> return os<<"Friday";</tt><tt> case Saturday:</tt><tt> return os<<"Satrurday";</tt><tt> case Sunday:</tt><tt> return os<<"Sunday";</tt><tt> default:</tt><tt> return os<<"Unknown";</tt><tt> } </tt><tt>}</tt></pre><h2> <a name="Heading20">Overloading the Subscripts Operator</a></h2><p>For various classes that contain arrays of elements, it's handy to overload the subscript operator to access a single element. Remember always to define two versions of the subscript operator: a <tt>const</tt> version and a non-<tt>const</tt> version. For example</p><pre><tt>class Ptr_collection</tt><tt>{</tt><tt>private :</tt><tt> void **ptr_array;</tt><tt> int elements;</tt><tt>public: </tt><tt> Ptr_collection() {}</tt><tt> //...</tt><tt> void * operator [] (int index) { return ptr_array[index];}</tt><tt> const void * operator [] (int index) const { return ptr_array[index];}</tt><tt>};</tt><tt>void f(const Ptr_collection & pointers)</tt><tt>{</tt><tt> const void *p = pointers[0]; //calls const version of operator [] </tt><tt> if ( p == 0) </tt><tt> return;</tt><tt> else</tt><tt> {</tt><tt> //...use p </tt><tt> }</tt><tt>}</tt></pre><h2> <a name="Heading21">Function Objects</a></h2><p>A function object is implemented as a class that contains an overloaded version of the function call operator. An instance of such a class can be used just like a function. Ordinary functions can have any number of arguments; therefore, operator <tt>()</tt> is exceptional among other operators because it can have an arbitrary number of parameters. In addition, it can have default arguments. In the following example, a function object implements a generic increment function:</p><pre><tt> #include <iostream></tt><tt>using namespace std;</tt><tt>class increment</tt><tt>{</tt><tt> //a generic increment function</tt><tt> public : template < class T > T operator() (T t) const { return ++t;}</tt><tt>};</tt><tt>void f(int n, const increment& incr)</tt><tt>{ </tt><tt> cout << incr(n); //output 1</tt><tt>}</tt><tt>int main()</tt><tt>{</tt><tt> int i = 0;</tt><tt> increment incr;</tt><tt> f(i, incr);</tt><tt> return 0;</tt><tt>}</tt></pre><h2> <a name="Heading22">Conclusions</a></h2><p>The concept of operator overloading is neither new nor C++ exclusive. It is one of the most fundamental facilities for implementing abstract data types and compound classes. In many ways, overloaded operators in C++ behave like ordinary member functions: They are inherited, they can be overloaded more than once, and they can be declared as either nonstatic members or as nonmember functions. However, several restrictions apply to overloaded operators. An overloaded operator has a fixed number of parameters, and it cannot have default arguments. In addition, the associativity and the precedence of an operator cannot be altered. Built-in operators have an interface, consisting of the number of operands to which the operator can be applied, whether the operator modifies any of its operands, and result that is returned by the operator. When you are overloading an operator, it is recommended that you conform to its built-in interface.</p><p>Conversion operators are a special type of overloaded operators. They differ from ordinary overloaded operators in two respects: They do not have a return value and they do not take any arguments. </p><p> </p><CENTER><P><HR> <A HREF="/publishers/que/series/professional/0789720221/index.htm"><img src="/publishers/que/series/professional/0789720221/button/contents.gif" WIDTH="128"HEIGHT="28" ALIGN="BOTTOM" ALT="Contents" BORDER="0"></A> <BR><BR><BR><p></P><P>© <A HREF="/publishers/que/series/professional/0789720221/copy.htm">Copyright 1999</A>, Macmillan Computer Publishing. Allrights reserved.</p></CENTER></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -