Programming 版 (精华区)

发信人: netiscpu (说不如做), 信区: Programming
标  题: C++ 编程准则与忠告 之 Functions
发信站: 紫 丁 香 (Sun Jul 26 11:21:30 1998), 转信




9 Functions

Back to index 

Unless otherwise stated, the following rules also apply to member
functions. 

9.1 Function Arguments

Back to index 

Rule 31 Do not use unspecified function arguments (ellipsis
notation). 


Rec. 41 Avoid functions with many arguments. 


Rec. 42 If a function stores a pointer to an object which is accessed
via an argument, let the argument have the type pointer. Use reference
arguments in other cases. 


Rec. 43 Use constant references (const &) instead of call-by-value,
unless using a pre-defined data type or a pointer. 


The best known function which uses unspecified arguments is printf().
The use of such functions is not advised since the strong type
checking provided by C++ is thereby avoided. Some of the possibilities
provided by unspecified function arguments can be attained by
overloading functions and by using default arguments. 

Functions having long lists of arguments look complicated, are
difficult to read, and can indicate poor design. In addition, they are
difficult to use and to maintain. 

By using references instead of pointers as function arguments, code
can be made more readable, especially within the function. A
disadvantage is that it is not easy to see which functions change the
values of their arguments. Member functions which store pointers which
have been provided as arguments should document this clearly by
declaring the argument as a pointer instead of as a reference. This
simplifies the code, since it is normal to store a pointer member as a
reference to an object. 

One difference between references and pointers is that there is no
null-reference in the language, whereas there is a null-pointer. This
means that an object must have been allocated before passing it to a
function. The advantage with this is that it is not necessary to test
the existence of the object within the function. 

C++ invokes functions according to call-by-value. This means that the
function arguments are copied to the stack via invocations of copy
constructors, which, for large objects, reduces performance. In
addition, destructors will be invoked when exiting the function. const
& arguments mean that only a reference to the object in question is
placed on the stack (call-by-reference) and that the object's state
(its instance variables) cannot be modified. (At least some const
member functions are necessary for such objects to be at all useful). 



Exception to Rule 31: No exceptions. 



Example 35 References instead of pointers 

  // Unnecessarily complicated use of pointers

  void addOneComplicated( int* integerPointer )

  { 

    *integerPointer += 1;

  }

        

  addOneComplicated( &j );

        

  // Write this way instead:

  void addOneEasy( int& integerReference )

  { 

    integerReference += 1;

  }

        

  addOneEasy( i );

        



Example 36 Different mechanisms for passing arguments 

  // a. A copy of the argument is created on the stack.

  //    The copy constructor is called on entry,

  //    and the destructor is called at exit from the function.

  //    This may lead to very inefficient code.

        

  void foo1( String s );

  String a;

  foo1( a );     // call-by-value

        

  // b. The actual argument is used by the function

  //    and it can be modified by the function.

        

  void foo2( String& s );

  String b;

  foo2( b );    // call-by-reference

        

  // c. The actual argument is used by the function

  //    but it cannot be modified by the function.

        

  void foo3( const String& s );

  String c;

  foo3( c );    // call-by-constant-reference

        

  // d. A pointer to the actual argument is used by the function.

  //    May lead to messy syntax when the function uses the argument.

        

  void foo4( const String* s );

  String d;

  foo4( &d );    // call-by-constant-pointer

        


9.2 Function Overloading

Back to index 

Rec. 44 When overloading functions, all variations should have the
same semantics (be used for the same purpose). 


Overloading of functions can be a powerful tool for creating a family
of related functions that only differ as to the type of data provided
as arguments. If not used properly (such as using functions with the
same name for different purposes), they can, however, cause
considerable confusion. 



Example 37 Example of the proper usage of function overloading 

  class String

  {

    public:              // Used like this:

      // ...            // String x = "abc123";

      int contains( const char c );      // int i = x.contains( 'b' );

      int contains( const char* cs );     // int j = x.contains( "bc1" );

      int contains( const String& s );    // int k = x.contains( x );

      // ...

  };

        


9.3 Formal Arguments

Back to index 

Rule 32 The names of formal arguments to functions are to be
specified and are to be the same both in the function declaration and
in the function definition. 


The names of formal arguments may be specified in both the function
declaration and the function definition in C++, even if these are
ignored by the compiler in the declaration. Providing names for
function arguments is a part of the function documentation. The name
of an argument may clarify how the argument is used, reducing the need
to include comments in, for example, a class definition. It is also
easier to refer to an argument in the documentation of a class if it
has a name. 



Exception to Rule 32: No exceptions! 



Example 38 Declaration of formal arguments 

  int setPoint( int, int );   // No !

  int setPoint( int x, int y );   // Good

        

  int

  setPoint( int x, int y )

  {

    // ...

  }

        


9.4 Return Types and Values

Back to index 

Rule 33 Always specify the return type of a function explicitly. 


Rule 34 A public function must never return a reference or a pointer
to a local variable. 


Functions, for which no return type is explicitly declared, implicitly
receive int as the return type. This can be confusing for a beginner,
since the compiler gives a warning for a missing return type. Because
of this, functions which return no value should specify void as the
return type. 

If a function returns a reference or a pointer to a local variable,
the memory to which it refers will already have been deallocated, when
this reference or pointer is used. The compiler may or may not give a
warning for this. 



Exception to Rule 33: No exceptions! 


Exception to Rule 34: No exceptions! 



Example 39 Functions which return no value should be specified as
having the return type void. 

  void

  strangeFunction( const char* before, const char* after )

  {

    // ...

  }

        


9.5 Inline Functions

Back to index 

Rule 35 Do not use the preprocessor directive #define to obtain more
efficient code; instead, use inline functions. 


Rec. 45 Use inline functions when they are really needed. 


See also 7.2. 
Inline functions have the advantage of often being faster to execute
than ordinary functions. The disadvantage in their use is that the
implementation becomes more exposed, since the definition of an inline
function must be placed in an include file for the class, while the
definition of an ordinary function may be placed in its own separate
file. 

A result of this is that a change in the implementation of an inline
function can require comprehensive re-compiling when the include file
is changed. This is true for traditional file-based programming
environments which use such mechanisms as make for compilation. 

The compiler is not compelled to actually make a function inline. The
decision criteria for this differ from one compiler to another. It is
often possible to set a compiler flag so that the compiler gives a
warning each time it does not make a function inline (contrary to the
declaration). "Outlined inlines" can result in programs that are both
unnecessarily large and slow. 

It may be appropriate to separate inline definitions from class
definitions and to place these in a separate file. 



Exception to Rule 35: No exceptions 



Example 40 Inline functions are better than macros 

  // Example of problems with #define "functions"

  #define SQUARE(x) ((x)*(x))

  int a = 2;

  int b = SQUARE(a++);  // b = (2 * 3) = 6

        

  // Inline functions are safer and easier to use than macros if you

  // need an ordinary function that would have been unacceptable for

  // efficiency reasons.

  // They are also easier to convert to ordinary functions later on.

  inline int square( int x )

  {

    return ( x * x );

  };

        

  int c = 2;

  int d = square( c++ );   // d = ( 2 * 2 ) = 4

        


9.6 Temporary Objects

Back to index 

Rec. 46 Minimize the number of temporary objects that are created as
return values from functions or as arguments to functions. 


Temporary objects are often created when objects are returned from
functions or when objects are given as arguments to functions. In
either case, a constructor for the object is first invoked; later, a
destructor is invoked. Large temporary objects make for inefficient
code. In some cases, errors are introduced when temporary objects are
created. It is important to keep this in mind when writing code. It is
especially inappropriate to have pointers to temporary objects, since
the lifetime of a temporary object is undefined. (See 18.7). 



Example 41 Temporary objects and one way of eliminating them 

  class BigObject  { double big[123456]; };

        

  // Example of a very inefficient function with respect to temporary objects:

  BigObject

  slowTransform( BigObject myBO )

  {

    // When entering slowTransform(), myBO is a copy of the function argument

    // provided by the user. -> A copy constructor for BigObject is executed.

    // ... Transform myBO in some way

    return myBO;   // Transformed myBO returned to the user

  }

  // When exiting slowTransform(), a copy of myBO is returned to the

  // user -> copy-constructor for BigObject is executed, again.

        

  // Much more efficient solution:

  BigObject&

  fastTransform( BigObject& myBO )

  {

    // When entering fastTransform(), myBO is the same object as the function

    // argument provided by the user. -> No copy-constructor is executed.

        

    // Transform myBO in some way

    return myBO;   // Transformed myBO is returned to the user.

  }

  // When exiting fastTransform(), the very same myBO is returned 

  // to the user. -> No copy constructor executed.

        

  void main()

  {

    BigObject BO;

    BO = slowTransform( BO );

    BO = fastTransform( BO );   // Same syntax as slowTransform() !!

  }

        


9.7 General

Back to index 

Rec. 47 Avoid long and complex functions. 


Long functions have disadvantages: 
1 If a function is too long, it can be difficult to comprehend.
Generally, it can be said that a function should not be longer than
two pages, since that is about how much that can be comprehended at
one time. 

2 If an error situation is discovered at the end of an extremely long
function, it may be difficult for the function to clean up after
itself and to "undo" as much as possible before reporting the error to
the calling function. By always using short functions, such an error
can be more exactly localized. 

Complex functions are difficult to test. If a function consists of 15
nested if statements, then there are 2**15 (or 32768) different
branches to test in a single function. 


Back to index 

Rule 36 Constants are to be defined using const or enum; never using
#define. 


Rule 37 Avoid the use of numeric values in code; use symbolic values
instead. 


The preprocessor performs a textual substitution for macros in the
source code which is then compiled. This has a number of negative
consequences. For example, if a constant has been defined using
#define, the name of the constant is not recognized in many debuggers.
If the constant is represented by an expression, this expression may
be evaluated differently for different instantiations, depending on
the scope of the name. In addition, macros are, at times, incorrectly
written. 

Numerical values in code ("Magic Numbers") should be viewed with
suspicion. They can be the cause of difficult problems if and when it
becomes necessary to change a value. A large amount of code can be
dependent on such a value never changing, the value can be used at a
number of places in the code (it may be difficult to locate all of
them), and values as such are rather anonymous (it may be that every
`2' in the code should not be changed to a `3'). 

From the point of view of portability, absolute values may be the
cause of more subtle problems. The type of a numeric value is
dependent on the implementation. Normally, the type of a numeric value
is defined as the smallest type which can contain the value. 



Exception to Rule 36: No exceptions. 


Exception to Rule 37: Certain numerical values have a well established
and clear meaning in a program. For example, '1' and '0' are often
used to represent `true' and `false' respectively. These may be used
directly in code without being considered to be "Magic". 



Example 42 Different ways of declaring constants. 

  // Constants using macros

  #define BUFSIZE 7       // No type checking

        

  // Constants using const

  const int bufSize = 7;       // Type checking takes place

        

  // Constants using enums

  enum SIZE { BufSize = 7 };   // Type checking takes place

        



Example 43 Declaration of const defined in another file 

  extern const char constantCharacter;

  extern const String fileName;

        
--

                              Enjoy Linux!
                          -----It's FREE!-----

※ 来源:.紫 丁 香 bbs.hit.edu.cn.[FROM: mtlab.hit.edu.cn]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:208.643毫秒