Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ Timesaving Techniques (2005) [eng].pdf
Скачиваний:
65
Добавлен:
16.08.2013
Размер:
8.35 Mб
Скачать

Adding Logging to the Application

401

If you have done everything right, you should see the following output in the console window of your favorite operating system:

$ ./a.exe Dump:

-----------------------

X = 3

-----------------------

Dump:

-----------------------

X = 2

-----------------------

Dump:

-----------------------

X = 5

-----------------------

Dump:

-----------------------

X = 5

-----------------------

Now, something here is obviously not right. We assigned the values to be 3, 2, 5, and 6.34. We should not be seeing the values of 5 in the last two positions. The third position is correct because that was the value assigned just before that print statement. But the fourth position follows an assignment to the string value “6.34”. After that is assigned, the value should no longer be 5. So what is going on here?

If we tracked through the code to see what’s going on, we’d eventually discover that the problem is in the assignment operator that accepts a string. However, tracing something this specific could take quite a while, because there are numerous ways that this particular bit of code could have been accessed. It’s relatively easy to spot the incorrect value when you are looking at the output from the program in this form, because we know what the expected output

is and we have a limited number of lines. Imagine, however, having to ransack hundreds of output statements and thousands of lines of code — not nearly so easy. So how do we make this task easier to debug?

The next section shows you how.

Adding Logging to the Application

To fix this problem, the best way to handle overloaded methods is to note problems as we encounter them. To do so, we can add some specialized logging to our application to track down which method is failing — and what’s going on. The following steps do that:

1. Reopen the source file for this technique.

In this example, I called the source file ch64.cpp.

2. Change the MyClass definition from its existing form to the code shown in Listing 64-2.

LISTING 64-2: THE MODIFIED MYCLASS LISTING

#define LOG(x) (cout << (x) << endl)

class MyClass

{

int _x; public:

MyClass(void)

{

setX(0);

LOG (“Null Constructor: Setting _x to 0”);

}

MyClass(int x)

{

setX(x);

LOG (“Int Constructor: Setting _x to x”);

}

MyClass( const MyClass& aCopy )

{

setX(aCopy._x);

LOG (“Copy Constructor: Setting _x to x”);

}

MyClass operator=(int x)

{

LOG(“Assignment Operator int”); LOG(x);

setX(x); return *this;

}

(continued)

402 Technique 64: Debugging Overloaded Methods

LISTING 64-2 (continued)

MyClass operator=(double d)

{

LOG(“Assignment Operator double”); LOG(d);

setX(d); return *this;

}

MyClass operator=( const char *str )

{

LOG(“Assignment Operator str”);

 

8

LOG(str);

 

9

setX( str

);

 

 

return *this;

}

void setX( int x )

{

LOG(“setX double”); _x = x;

LOG(_x);

}

void setX( double d )

{

LOG(“setX double”); _x = (int)d; LOG(_x);

}

void setX( const char *str )

{

LOG(“setX str”); int x = atoi(str); LOG(_x);

}

int getX(void)

{

return _x;

}

};

The code above is substantially the same as the original listing, but it now contains logging statements that can be used in a debug environment to output details about what is going on in the program. As you can see, we have added a LOG statement to each of the methods, so that we are printing out each call and change within the code. For example, in the operator= method that accepts a string argument, we have added lines

8 and 9, logging the name of the method and the value we are changing. Let’s see what

good this does for us in determining the source of the problem.

3. Compile the source file with your favorite compiler, on your favorite operating system.

4. Run the resulting program on your favorite operating system.

If you have done everything right, you should see the following output from the program on the console window:

$ ./a.exe

 

 

setX double

 

 

3

 

 

Int Constructor: Setting _x to x

 

 

Dump:

 

 

-----------------------

 

 

X = 3

 

 

-----------------------

 

 

Assignment Operator double

 

 

2

 

 

setX double

 

 

2

 

 

setX double

 

 

2

 

 

Copy Constructor: Setting _x to x

 

 

Dump:

 

 

-----------------------

 

 

X = 2

 

 

-----------------------

 

 

Assignment Operator int

 

 

5

 

 

setX double

 

 

5

 

 

setX double

 

 

5

 

 

Copy Constructor: Setting _x to x

 

 

Dump:

 

 

-----------------------

 

 

X = 5

 

 

-----------------------

 

 

Assignment Operator str

 

 

6.34

 

 

setX str

 

10

5

 

setX double

 

5

 

 

Copy Constructor: Setting _x to x

 

 

Dump:

 

 

-----------------------

 

 

X = 5

 

 

Adding Logging to the Application

403

Now, looking at the output from this code makes it pretty obvious where the problem lies — but we can do one more thing to make it even more obvious. We can add asserts to our setX functions to verify that what we expect on output is what we really get. We haven’t fixed anything yet, but we now know that the reason the value isn’t being set is because the operator= that accepts a string is sending the proper value to the setX that accepts a string, but the value is not being changed (as shown at 10 in the output listing above). The assert will tell us exactly when the value is not changed.

Suppose we modified the setX that accepted a string to read like this:

void setX( const char *str )

{

LOG(“setX str”);

 

11

int x =

atoi(str);

 

LOG(_x);

 

assert(

_x == x );

 

 

}

This modification adds a post-condition to the setX method that asserts that the value of the variable x is equal to the value of the variable x.

Wait a minute — we’re expecting the output to be the same as the integer value of the string that was input — correct? In this case, the code would fail because the output is not correct; we’d have our culprit. The combination of logging and asserts — built into the overloaded methods — makes the problem easy to find. It also points out a serious problem with overloaded methods: They complicate debugging and maintenance. After we recognize that the problem is a local variable assigned in the method instead of the class member variable x, we can fix the problem by removing the local x variable shown at 11 above.

You don’t have to use the features of the language just because they exist. Sometimes it’s better not to.

The program will work properly now. What you’ve seen in this technique is how to trace through the overloaded methods in a class, logging information and assuring that the values coming out of the method are what you expect them to be.

Part X

The Scary (or Fun!) Stuff