I l@ve RuBoard |
![]() ![]() |
2.3 Providing Default Parameter ValuesPrinting a trace of our bubble sort program to ofil required that I make ofil available to the multiple functions I wished to debug. Because I was responding to unexpected and unwelcomed behavior, I chose the quickest solution to the problem of making an object visible across multiple functions: I defined ofil at file scope. As a general programming rule, however, it is better to communicate between functions using parameters rather than use objects defined at file scope. One reason is that a function that is dependent on an object defined at file scope is harder to reuse in a different context. The function is also harder to modify: We must understand not only the logic specific to the function but also the logic of the objects defined at file scope. Let's see how we might revise bubble_sort() to do away with its reliance on the file scope instance of ofil: void bubble_sort( vector<int> &vec, ofstream &ofil ) { for ( int ix = 0; ix < vec.size(); ++ix ) for ( int jx = ix+1; jx < vec.size(); ++jx ) if ( vec[ ix ] > vec[ jx ] ) { ofil << "about to call swap! ix: " << ix << " jx: " << jx << "\tswapping: " << vec[ix] << " with " << vec[ jx ] << endl; swap( vec[ix], vec[jx], ofil ); } } Although this technique removes our reliance on the file scope instance of ofil, it introduces a number of potentially vexing problems. Every call to bubble_sort() now requires our user to pass in an ofstream class object. Also, we're generating this information without the user being able to turn it off. In the general case, when things are going well, no one is interested in this information. We'd prefer not to bother the user either with having to specify an output stream or with having to turn anything off. By default, we'd like not to generate information. However, we'd like to allow the interested user to generate the information and specify the file into which the information should be stored. How might we do that? C++ allows us to associate a default value for all or a subset of parameters. In our case, we provide the ofstream pointer parameter with a default value of 0: void bubble_sort( vector<int> &vec, ofstream *ofil = 0 ) { for ( int ix = 0; ix < vec.size(); ++ix ) for ( int jx = ix+1; jx < vec.size(); ++jx ) if ( vec[ ix ] > vec[ jx ] ) { if ( ofil != 0 ) (*ofil) << "about to call swap! ix: " << ix << " jx: " << jx << "\tswapping: " << vec[ix] << " with " << vec[ jx ] << endl; swap( vec[ix], vec[jx], ofil ); } } This revised version of bubble_sort() declares its second parameter as a pointer to an ofstream object rather than as a reference. We must make this change to provide a default value of 0, indicating that no ofstream object is addressed. Unlike a pointer, a reference cannot be set to 0. A reference must always refer to some object. An invocation of bubble_sort() with a single argument generates no debugging information. An invocation with a second argument that addresses an ofstream object generates the debugging information: int main() { int ia[ 8 ] = { 8, 34, 3, 13, 1, 21, 5, 2 }; vector<int> vec( ia, ia+8 ); // no debug information -- // it is as if we invoked bubble_sort( vec, 0 ); bubble_sort( vec ); display( vec ); // ok: debug information generated ... ofstream ofil( "data.txt" ); bubble_sort( vec, &ofil ); display( vec, ofil ); } The implementation of display() presents a different situation. Currently, it hard-codes the output to cout. In the general case, cout is fine. In some cases, however, users are likely to prefer to supply an alternative target, such as a file. Our implementation must support both uses of display() in main(). Our solution is to make cout the default ostream parameter: void display( const vector<int> &vec, ostream &os = cout ) { for ( int ix = 0; ix < vec.size(); ++ix ) os << vec[ix] << ' '; os << endl; } There are two somewhat unintuitive rules about providing default parameters. The first rule is that default values are resolved positionally beginning with the right-most parameter. If a parameter is provided with a default value, all the parameters to its right must also have a default value. The following, for example, is illegal: // error: no default value for vec void display( ostream &os = cout, const vector<int> &vec ); The second rule is that the default value can be specified only once ?either in the declaration or in the definition of the function, but not both. So where should we specify the default value? Typically, a function declaration is placed in a header file. This header file is then included by files that wish to use the function. (Recall that we included the cstdlib header file to include the declaration of the exit() library function.) The definition of a function typically is placed in a program text file. This file is compiled once and is linked to our program whenever we wish to use the function. The header file, that is, provides the greater visibility of the function. (Header files are discussed in more detail in Section 2.9.) Because of its greater visibility, we place the default value in the function declaration rather than in the function definition. For example, the declaration and definition of display() generally would look like this: // header file declaration specifies default value // let's call the header file: NumericSeq.h void display( const vector<int>&, ostream&=cout ); // program text file definition includes header file // the definition itself does not specify the default value #include "NumericSeq.h" void display( const vector<int> &vec, ostream &os ) { for ( int ix = 0; ix < vec.size(); ++ix ) os << vec[ix] << ' '; os << endl; } |
I l@ve RuBoard |
![]() ![]() |