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

Creating the Multiple-Search-Path Class

367

If you are storing various configuration files for your system, it’s unlikely that the user will store all the files in one place. For example, if your program has a set of configuration files, a set of definition files, a set of output report files, and so on, the user will probably want them stored in different locations. It’s also likely that the user will sometimes forget which ones go where and put them in the wrong places. Rather than making the users do the work of finding all the files, you should define a set of possible search paths for the user. This approach saves time for the user, spares the programmer some grief, makes your application better received, and makes life a bit easier for all concerned. If you require that the user find each file, rather than search for it yourself in the most likely places, you make life harder for the user. If you make life harder for the user, they will use someone else’s program.

Creating the Multiple-

Search-Path Class

A utility class that allows the user to find a file by using multiple search paths simply makes good sense. The following steps create such a class, called

MultiPathFile:

1. In the code editor of your choice, create a new file to hold the code for the implementation of the source file.

In this example, the file is named ch61.cpp, although you can use whatever you choose.

2. Type the code from Listing 61-1 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 61-1: THE MULTIPLE-SEARCH-PATH UTILITY CLASS

#include <string> #include <vector> #include <fstream> #include <iostream> #include <sys/stat.h>

using namespace std;

class DirectoryList

{

private:

vector< string > _entries; public:

DirectoryList(void)

{

}

DirectoryList( const char *strDir )

{

_entries.insert( _entries.end(), strDir );

}

DirectoryList( const DirectoryList& aCopy )

{

vector<string>::const_iterator iter;

for ( iter = aCopy._entries.begin(); iter != aCopy._entries.end(); ++iter )

_entries.insert( _entries.end(), (*iter) );

}

 

 

bool is_dir( const char *strDir )

 

1

{

 

struct stat st;

 

 

if ( stat( strDir, &st )

3

{ == 0 )

if ( st.st_mode & S_IFDIR )

 

return true;

 

 

}

 

 

(continued)

368 Technique 61: Opening a File Using Multiple Paths

LISTING 61-1 (continued)

return false;

}

void addEntry( const char *strDir )

{

_entries.insert( _entries.end(), strDir );

}

void removeEntry( const char *strDir )

{

vector<string>::iterator iter; for ( iter = _entries.begin();

iter != _entries.end(); ++iter )

{

if ( (*iter) == strDir )

{

_entries.erase( iter );

}

}

}

int count(void) const

{

return _entries.size();

}

DirectoryList operator+=( const char *strDir )

{

_entries.insert( _entries.end(), strDir );

return *this;

}

DirectoryList operator-=( const char *strDir )

{

removeEntry( strDir ); return *this;

}

string getEntry( int idx )

{

if ( idx < 0 || idx > count()-1 ) throw “DirectoryList: Array

Index out of range”;

return _entries[ idx ];

}

};

class MultiPathFile

{

private:

DirectoryList _pathList; ifstream _in; string _path;

public:

MultiPathFile(void) : _path(“”)

{

}

MultiPathFile( const DirectoryList& aPathList )

:_pathList( aPathList ), _path(“”)

{

}

MultiPathFile( const char *strDir )

:_pathList( strDir ), _path(“”)

{

}

void addPath( const char *strDir )

{

if ( _pathList.is_dir( strDir ) ) _pathList.addEntry( strDir );

else

printf(“Invalid path: [%s]\n”, strDir );

}

void removePath( const char *strDir )

{

_pathList.removeEntry( strDir );

}

 

 

bool open( const char *strFileName )

 

2

{

 

for ( int i=0; i<_pathList.count(); ++i )

{

string sDir = _pathList.getEntry( i );

string sFullPath = sDir + strFileName;

_in.open( sFullPath.c_str(), ios::in );

Testing the Multiple-Search-Path Class

369

if ( !_in.fail() )

{

_path = sDir; return true;

}

_in.clear();

}

return false;

}

void close()

{

_in.close();

}

ifstream& file(void)

{

return _in;

}

string CurrentPath(void)

{

return _path;

}

};

3. Save the source code in the code-editor application.

The code above breaks down into two classes:

The DirectoryList class: This class simply maintains a list of various directories in the system and allows the programmer to have a single, consistent way to access directory names. There is nothing really surprising in this class; it is just a wrapper around the Standard Template Library (STL) vector class that does a bit of extra checking to see if a given name is a directory. (See the is_dir method, shown at 1.)

The MultiPathFile class: This is really an extended version of the basic file classes supplied by the STL. It maintains a list of directories to search by using the DirectoryList class to

hold the various directories. When an open request is received via the open method, as shown at 2, the class iterates through the various directories in its list and tries to open the file in each one. If the file is found in a given directory, it stores the directory path where the file was found and returns a handle allowing the programmer to access the file.

These two classes (DirectoryList and MultiPathFile) do all of the work of managing the search paths and then utilizing those search paths to open and manipulate the file. Notice the use of stat (a standard C function ) shown at the line marked 3 to check whether an input path is valid and is a directory.

Note that you will need to add search paths to the system using the path delimiter (which is the forward or backward slash, depending on the operating system you’re working with) appended to the end of the path. That is, you have to enter c:/windows/ rather than simply c:/windows, because the system will not append the delimiter for you. This could easily be fixed, but would require even more code in what is already a fairly long listing.

Testing the Multiple-Search-

Path Class

After you create a class, you should create a test driver that not only ensures that your code is correct, but also shows people how to use your code. The following steps tell you how:

1. In the code editor of your choice, reopen the source file to hold the code for your test program.

In this example, I named the test program ch61.cpp.

370 Technique 61: Opening a File Using Multiple Paths

2. Type the code from Listing 61-2 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

LISTING 61-2: THE MULTIPLE-SEARCH-PATH TEST PROGRAM

void display_file( ifstream& in )

{

// Display the first 100 characters cout << endl;

for ( int i=0; i<100; ++i )

{

char c; in.get(c);

if ( !in.fail() ) cout << c;

else

break;

}

cout << endl;

}

int main( int argc, char **argv )

{

MultiPathFile paths;

//First, add in all the paths for ( int i=1; i<argc; ++i )

{

paths.addPath( argv[i] );

}

//Now ask them what they want to do. bool bDone = false;

while ( !bDone )

{

char szPath[ 256 ]; char szFile[ 256 ];

printf(“Options:\n”); printf(“(1) Add a new search

path\n”);

printf(“(2) Open a file\n”); printf(“(3) Exit the

program\n\n”);

printf(“What do you want to do? “);

int option = 0; scanf(“%d”, &option );

getchar();

 

 

 

switch ( option )

 

 

 

{

 

 

4

case 1:

 

 

printf(“Enter search

 

path to add: “);

 

 

memset( szPath, 0,

 

 

sizeof(szPath) );

 

 

 

gets(szPath);

 

 

if ( strlen(szPath) )

 

paths.addPath(

 

 

szPath );

 

 

break;

 

 

5

case 2:

 

 

 

 

 

printf(“Enter file to

 

open: “);

 

 

memset( szFile, 0,

 

 

sizeof(szFile) );

 

 

gets(szFile);

 

 

if ( strlen(szFile) )

 

if ( !paths.open(

 

 

szFile ) )

 

 

 

printf(“Error

 

 

finding file

 

%s\n”,

 

 

 

szFile );

 

else

 

 

{

printf(“File

 

 

 

 

found at:

 

 

[%s]\n”,

 

 

 

paths.Curren

 

tPath().c_st

 

r() );

 

 

 

display_file(

 

 

paths.file()

}

);

7

break;

 

 

6

case 3:

 

 

bDone = true;

 

break;

default:

printf(“Invalid

Option\n”);

break;

}

}

return 0;

}

Testing the Multiple-Search-Path Class

371

The above code tests the multiple search paths to open files. When run, the program allows the user to add various search paths to their list of directories to use, then allows them to search for the file using those paths. If the user wants to add a new search path, he enters ‘1’ at the prompt, which falls into the code at 4. This code gets a path name from the user and adds it to the search path list. If the user wants to find a file, he enters ‘2’ at the prompt, and the program prompts for

the filename to search for (see

5). If it is found,

it will be printed out along with the path that it

was found in. Finally, entering ‘3’ at the prompt ter-

minates the loop and exits the program (see 6).

3. Save the source code in your code editor and then close the editor application.

4. Compile the application using your favorite compiler on your favorite operating system.

5. Run the application in the console window of your favorite operating system.

If you have done everything right, you should see a session that looks something like this:

$ ./a.exe Options:

(1) Add a new search path

(2) Open a file

(3) Exit the program

What do you want to do? 1

 

8

Enter search path to add: c:/matt/

 

Options:

 

(1)

Add a new search path

 

 

(2)

Open a file

 

 

(3)

Exit the program

 

 

What do you want to do? 1

 

9

Enter search path to add: c:/work/

 

Options:

 

(1)

Add a new search path

 

 

(2)

Open a file

 

 

(3)

Exit the program

 

 

What do you want to do? 1

 

10

Enter search path to add: c:/windows/

 

Options:

 

 

(1) Add a new search path

 

 

(2) Open a file

 

 

 

(3) Exit the program

 

 

What do you want to do? 2

 

11

Enter file to open: DATECL.h

 

File found at: [c:/work/]

12

/*

 

 

 

 

 

 

*+-------------------------------------------

 

 

 

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

 

 

*| Header.......

: DATE

 

 

Options:

 

 

 

(1)Add a new search path

(2)Open a file

(3)Exit the program

What do you want to do? 3

As you can see, the utility class went through the list

of paths I gave it (shown at lines

8,

 

9, and 10),

filename that was

found the one that matched the

 

 

 

input (shown at 11), opened the file, and allowed

addition, it returned the path to

me to read it. In

 

 

where the file was found (shown at

 

12); I can

application.

output that data to the user of the

 

 

The file is displayed via the display_file call in Listing 61-2 at line 7.

Improving the class

The class as it stands is very useful, but would benefit from a capability that saves the paths to some form of persistent storage and then reads them back from the storage file at program startup. The user would only have to enter the paths once. In addition, the program does not use relative file paths for some operating systems (such as ~ in Unix), because of the way in which the open is done. This could be improved as well.

Part IX

Debugging C++

Applications

62 Building Tracing

into Your

Technique Applications

Save Time By

Understanding the benefits of building tracing into your applications

Creating a flow trace class

Testing the flow trace class

Building in tracing after the fact

Testing your code

If you have ever tried to debug an application that you didn’t write, you know that the biggest challenge is simply figuring out how the program got into its present state to begin with. Quite often, while the individual components of a system are well documented, the overall flow of the system is not. There is no real way for you to know how the program got from the initial entry point to the point at which the problem occurred without

stepping through each and every line in the debugger. Given the number of levels in the average production-quality C++ program, it can take hours to get from the beginning of the program to the problem spot. Wouldn’t it be nice if you could just look through the source code for potential problems and trace through the code path that you knew the system was taking to get from point A (an entry point into the system) to point B (where the problem occurs)? Of course it would.

In general, what prevents us from having the data we need to trace from one point in the program to the core of the system is a lack of information about the path that is taken. Most debuggers can show you a call stack of how you got somewhere, but that stack goes away when you stop running the program. Worse, because the call stack shows you absolutely everything that goes on, you will often find yourself chasing problems in the system libraries and language code, rather than the more likely problems in your own code. What you really need to know is the path through your code that was used, not every call into the allocation libraries or string functions. This means we need a way to trace through our own code only, and show where we are at any given time.

Of course, the obvious solution here is simply to build a tracing capability into our applications. Okay, how do you go about this? The easiest way is to create a C++ class that can “report” where we are in the program, and to then have that class talk to a “manager” process that can print out a complete trace tree, showing how we got there. That, in a nutshell, is the purpose of this technique.

376 Technique 62: Building Tracing into Your Applications

Implementing the

Flow Trace Class

1. In the code editor of your choice, create a new file to hold the code for the definition of the source file.

In this example, the file is named ch62.cpp,

First, we need to implement the definition of the flow

although you can use whatever you choose.

trace class. The flow trace class allows us to track

2. Type the code from Listing 62-1 into your file.

what happens in the program, and to quickly and

easily display a list of all of the functions that were

Better yet, copy the code from the source file on

called from the point the process was begun to when

this book’s companion Web site.

the problem occurred.

 

LISTING 62-1: THE FLOW TRACE CLASS DEFINITION

#include <string> #include <stack> #include <vector>

class debugFlowTracer

{

private:

std::string m_sFlowName;

std::vector< debugFlowTracer > m_activefunctionStack;

bool

m_bRemoved;

protected:

 

virtual void AddFlow();

 

virtual void RemoveFlow();

 

public:

 

debugFlowTracer(void)

 

{

 

m_sFlowName = “Unknown”;

 

m_bRemoved = false;

 

AddFlow();

 

}

 

debugFlowTracer(const char *strFlow)

{

m_sFlowName = strFlow; m_bRemoved = false; AddFlow();

}

debugFlowTracer( const debugFlowTracer& aCopy )

{

m_sFlowName = aCopy.m_sFlowName;

std::vector< debugFlowTracer >::const_iterator iter;

for ( iter = aCopy.m_activefunctionStack.begin(); iter != aCopy.m_activefunctionStack.end(); ++iter ) m_activefunctionStack.insert( m_activefunctionStack.end(), (*iter) );

}

Implementing the Flow Trace Class

377

~debugFlowTracer(void)

{

if ( !m_bRemoved ) RemoveFlow(); m_bRemoved = true;

}

std::string Name()

{

return m_sFlowName;

}

void AddSubFlow( debugFlowTracer& cSubFlow )

{

// Just push it on top of the active function stack. m_activefunctionStack.insert( m_activefunctionStack.end(), cSubFlow );

}

void PrintStack(int iLevel)

{

std::vector< debugFlowTracer >::iterator iter;

for ( iter = m_activefunctionStack.begin(); iter != m_activefunctionStack.end(); ++iter )

{

for ( int i=0; i<iLevel; ++i ) putchar (‘\t’);

printf(“%s\n”, (*iter).Name().c_str() ); (*iter).PrintStack(iLevel+1);

}

}

};

The flow trace object (debugFlowTrace) contains a name that you can use for defining specific entry points into the system — such as when the user starts using a feature such as saving a file. It also contains a list of sub-flows, which are simply the functions that are entered when the flow begins. If you start in function one, call function two and four, in which function two calls function three, you would have a trace that looked like this:

Function One

Function Two

Function Three

Function Four

This listing could be easily generated by the flow trace object.

3. Save the source code as a file in the code editor of your choice.

4. Append the code from Listing 62-2 to your file.

This will contain the debugFlowTracerManager class.

Better yet, copy the code from the source file on this book’s companion Web site.

378

Technique 62: Building Tracing into Your Applications

 

 

LISTING 62-2: THE FLOW TRACE CLASS AND MANAGER IMPLEMENTATION

 

 

 

 

 

class debugFlowTracerManager

 

 

{

 

 

 

private:

 

 

std::stack< debugFlowTracer> m_functionStack;

 

 

static debugFlowTracerManager *m_Instance;

 

 

public:

 

1

static debugFlowTracerManager *Instance()

 

{

 

 

if ( m_Instance == NULL )

m_Instance = new debugFlowTracerManager(); return m_Instance;

}

void addFlow( debugFlowTracer& cFlow )

{

m_functionStack.push( cFlow );

}

void removeFlow(debugFlowTracer& cFlow)

{

if ( m_functionStack.empty() ) return;

// Get the top element.

debugFlowTracer t = m_functionStack.top();

// Remove it. m_functionStack.pop();

// If there is anything left, add it. if ( m_functionStack.empty() )

{

printf(“Flow [%s]:\n”, t.Name().c_str() ); t.PrintStack(0);

}

else

m_functionStack.top().AddSubFlow( t );

}

private:

debugFlowTracerManager()

{

}

debugFlowTracerManager(const debugFlowTracerManager& aCopy )

{

}

virtual ~debugFlowTracerManager(void)

{

}

};