Item 21: Don’t try to return a reference when you must return...

  1. 1. Part 4: Designs and Declarations
  2. 2. Things to remember
  3. 3. Key words
    1. 3.1. Stack and Heap(Isn’t std::stack or heap(data structure) “heap(data structure)”)):
  4. 4. Code analysis
  5. 5. Code

This article is one of <Effective C++> reading notes.

Part 4: Designs and Declarations

Item 21: Don’t try to return a reference when you must return an object.


Things to remember

  • Never return a pointer or reference to a local stack object, a reference to a heap-allocated object, or a pointer or reference to a local static object if there is a chance that more than one such object will be needed.

Key words

Stack and Heap(Isn’t std::stack or heap(data structure) “heap(data structure)”)):

  • A function can create a new object in only two ways: on the stack or on the heap.
  • Value types are created on the stack, and reference types are created on the heap.
  • About stack, system will allocate memory to the instance of object automatically, and to the heap, you must allocate memory to the instance of object with new or malloc manually.
  • When function ends, system will automatically free the memory area of the stack, but to the heap, you must free the memory area manually with free or delete, else it will result in the memory leak.

Code analysis

Bad code 1:

1
2
3
4
5
6
7
8
/************************************************************************
* warning! bad code!
************************************************************************/
inline const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
trueRational result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}

A more serious problem is that this function returns a reference to result, but result is a local object, and local objects are destroyed when the function exits. This version of operator*, then, doesn’t return a reference to a Rational — it returns a reference to a former and empty Rational.

Bad code 2:

1
2
3
4
5
inline const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
trueRational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
return *result;
}

Will, who will apply delete to the object conjured up by your use of new?

Bad code 3:

1
2
3
4
5
6
inline const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
truestatic Rational result;
result = ...;
result result;
}

All designs employing the use of static objects, this one immediately raises our thread-safety hackles.
The expression if (operator==(operator(a, b), operator(c, d))) will always evaluate to true, regardless of the value of a, b, c, and d!
In any case, should avoid the use of global variables and static variables within a function.

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include "stdafx.h"
#include <iostream>
class Rational {
public:
trueRational(int numberator = 0, int denominator = 1) \
: n(numberator), d(denominator) { }
truedouble getNum() { if (d != 0) return n / d; };
private:
truedouble n, d;
friend const Rational operator*(const Rational& lhs, const Rational &rhs);
/**********************************************************************
true* warning! bad code!
true**********************************************************************/
// friend const Rational& operator*(const Rational& lhs, const Rational &rhs); };
trueinline const Rational operator*(const Rational& lhs, const Rational& rhs) {
truetruereturn Rational(lhs.n * rhs.n, lhs.d * rhs.d);
true}
true/************************************************************************
true* warning! bad code!
true************************************************************************/
true// inline const Rational& operator*(const Rational& lhs, const Rational& rhs)
true// {
true// Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
true//
true// local objects are destroyed when the function exits.
true// return result;
true// }
true// inline const Rational& operator*(const Rational& lhs, const Rational& rhs)
true// {
true// Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);
true//
true// who will apply delete to the object conjured up by your use of new ?
true// return *result;
true// }
true// inline const Rational& operator*(const Rational& lhs, const Rational& rhs)
true// {
true// static Rational result;
true// result = ...;
true//
true// raises our thread-safety hackles
true//
true// if (operator==(operator*(a, b), operator*(c, d)) will always evaluate to true
true// result result;
true// }
}
int main()
{
trueRational a(1, 2);
trueRational b(3, 5);
trueRational c = a * b;
truestd::cout << c.getNum() << std::endl; system("pause");
return 0;
}