Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C and C++ language courses for corporations. He is the author of All On C, C for COBOL Programmers, and UNIX for MS-DOS Users, and was a member of the ANSI C committee. He also does custom C/C++ programming and provides SystemArchitectonicssm services. His address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax questions for Ken to (919) 489-5239. Ken also receives email at kpugh@allen.com (Internet) and on Compuserve 70125,1142.
Q
I am working on a text retrieval/display project using C++. I have a text display object that I call TextItem. I would like the constructor for TextItem to fail and return a null pointer if it can't find the file it is supposed to display, but I can't determine how to do this. Do I have to overload the new operator for every object that I want to have behave in this way?
Ronald Sawyer
Camp Hill, PAA
I'm afraid there's no way you can overload operator new to get what you want. And you won't have much luck getting a constructor to return a value, either C++ won't allow it. But I can show you a way to respond effectively to constructor failures. For the benefit of C programmers, I'll start with a few words of explanation. A constructor is a function that is called implicitly when a variable is declared. Suppose the TextItem class looks like this:
class TextItem { public: TextItem(char * file_name); ... private: char * m_file_name; FILE * file_pointer; };A constructor for TestItem, using stdio functions, might look like:
TextItem::TextItem(char *file_name) { m_file_name = file_name; file_pointer = fopen(file_name,"r"); }(A constructor always has the same name as its class.)To declare an object of type TextItem you could write:
TextItem my_text_item("my_file");As I said earlier, in C++, a constructor does not return a value. Nor would we expect a declaration to produce a value where would we store such a value? So merely, declaring an object as we have above gives us no direct way to check on the success of the constructor. What about operater new? The new operator allocates memory for an object and then calls the constructor for that object. new returns a pointer to the allocated memory. For example:
TextItem *my_pointer = new TextItem("my_file");if new cannot allocate memory, it returns a null pointer. Now, in your situation you would like new to return a null pointer if the constructor fails, such as when it tries to open an unavailable file. We can overload new for each class; therefore it does not necessarily have to work the same way as the general new operator. But there is a communication problem between new and the constructor. new does not get the parameters that are passed to the constructor. new only receives information as to the size and number of objects to allocate. Therefore new cannot check if the file is unavailable and thus return a null pointer at that point.You can handle constructor failures a number of interesting ways. In this month's column I describe a fairly simple method. The approach involves creating a private member in each object to keep track of whether the object is in a valid state. This member's value is set by the constructor and any other member functions that may change the state. A public function can return the validity state. All member functions can test the state of an object before operating on it. The corresponding class might look like this:
class TextItem { public: TextItem(char * file_name); Boolean is_valid(); ... private: char * m_file_name; FILE * file_pointer; Boolean valid; }; TextItem::TextItem(char *file_name) { valid = FALSE; m_file_name = file_name; file_pointer = fopen(file_name,"r"); if (file_pointer != NULL) valid = TRUE; } Boolean TextItem::is_valid() { return valid; }The Boolean type could either be an enumeration or a class of its own. The following code shows how such a class might be used:
TextItem my_text_item("my_file"); if (!my_text_item.is_valid()) { // Do appropriate actions TextItem *my_pointer = new TextItem("my_file"); if (!my_pointer.is_valid ()) { // Free allocated memory delete my_pointer; //Do any other appropriate actionsIn some cases, you may want to use an object that has been constructed, but is not necessarily valid. (You can create an "unfinished" object by calling a constructor with no parameters. Such a constructor can mark the object as invalid.) The following code shows such a constructor and code that uses invalid objects.
class TextItem { public: TextItem(char * file_name); TextItem(); Boolean open(char *file_name); ... private: char * m_file_name; FILE * file_pointer; Boolean valid; };The code for these function might be:
TextItem::TextItem() { valid = FALSE; } Boolean TextItem::open(char *file_name) { // Check to see if already open //if (valid) { fclose(file_pointer); m_file_name = ""; } valid = FALSE; m_file_name = file_name; file_pointer = fopen(file_name,"r"); if (file_pointer != NULL) valid = TRUE; return valid; }Now the user code may appear as:
TextItem my_text_item("my_file"); if (!my_text_item.is_valid()) { // Try alternative file if (!my_text_item.open( "another_file")) { // Do appropriate action or // try againSome programmers may wish to reuse the file_pointer member as a validity flag, instead of using a separate member. Although reusing file_pointer may save a byte or two per object, I feel that having a separate state member keeps the code more readable and maintainable.Before going onto the next question, I'd like to complete the picture with a discussion of destructors. The system always calls an object's destructor when the object goes out of scope. If you don't include a destructor, C++ will provide a default constructor. But, if your object contains a validity flag, then you will probably also need to create a non-trivial destructor for that object. The destructor should test the validity of the object. If the object is valid, the destructor should in most cases perform the reverse of the actions that made the object valid. For example:
TextItem::~TextItem() { if (valid) { fclose(file_pointer); } }This same valid state technique works with objects other than those that open and close files. Objects that allocate memory for themselves or have complex internal relationships between members are prime candidates for a validity flag.
Using qsort in C++
QAfter reading the column "Question & Answers: Using C Libraries in C++" in the May, 1994 issue of The C User's Journal I came across a techinfo file from Borland that discussed the use of the qsort function in C++. The following text and code is the contents of the file TI659.ASC, dated October 19, 1993, entitled "An Example of Using qsort in C++ Mode:"
Due to the strict type checking enforced by the C++ language, one must insure that the types of the parameters to qsort match exactly.
The types of the parameters passed to the compare function are the area for grief here. By using a typedef which typecasts the compare function to have the types required by the ANSI C prototype for qsort, C++ can be made to accept our code.
//------------------------------------- // QSORTX.CPP - using qsort() in // C++ mode #include <stdlib.h> typedef int (*cmp_func)(const void *, const void *); int compare( const int *one, const int *two ) { if (*one > *two) return -1 else return 1; } // end of compare() int a[3] = { 50, 10, 20 }; main() { qsort(a, 3, sizeof(a[0]),compare); // Does not compile in C++ qsort(a, 3, sizeof(a[0]), (cmp_func)compare); // Does compile } // end of main()I think this techinfo file presents an interesting approach by typecasting the function; however, in so doing it disables C++'s type-checking. I hope this information might help readers make use of existing functions.Duane Clark
Kingsville, TXA
As I noted in my answer, I prefer using this approach, as the compare function accepts pointers to members of a particular class. However, this approach is not guaranteed to be portable, as P.J. Plauger pointed out. On some machines especially ancient ones, the representation of a void pointer is not necessarily the same as the representation of a pointer to a data type such as int. I worked on one machine which used 12-bit chars and could hold five chars in an addressable word. (In case you are wondering, the machine had a 60-bit word). However, I have not heard of a machine in common use that uses inconsistent pointer representations. If anyone does know of such a machine, please let me know.
The alternative to Borland's approach is to use a compare function such as
int compare( const void *one, const void *two );but this approach has little appeal to me. In fact, it is somewhat repugnant. The above function prototype does not specify the exact types the function expects. I can pass any type of pointer to this function and the compiler will not complain. Chances are that the function will at least not bomb, since it is not writing to the addresses being passed. But that is little consolation.
The compare Function and qsort
QPhil Bolduc presented the problem that a member function cannot be used by qsort but this is only a problem for non-static member functions (which can also access private members of the class)! For example:
#include <stdlib.h> class DataType { public: static int compare(const void *, const void *); }; extern int operator<(const DataType &, const DataType &); extern int operator==(const DataType &, const DataType &); void sortDataType(DataType *array, int count) { qsort(array, count, sizeof(DataType), &DataType::compare); } int DataType::compare(const void *left, const void *right) { const DataType *dtLeft = (const DataType *)left; const DataType *dtRight = (const DataType *)right; return (*dtLeft < *dtRight) ? -1 : (*dtLeft == *dtRight) ? 0 : 1; }Note that without the static the type of &DataType::compare would be int (DataType::*)(const void *, const void *), but with the static it's int (*)(const void *, const void *). The above also compiles with Symantec C++ (V6.1).Tony Cook
A
Thanks for the reply. Tony Beleta of Barcelona, Spain, sent in a similar letter. My original answer centered around the possible incompatibilities between calling sequences of C and C+ functions.
If qsort is compiled as a C++ function, then you have two alternatives: You can make compare a static member of the class, as you have shown, or you can add comparison functions to the class.
For our readers new to C++, a static class function is one that is called without an instance of the class. A static class function does not receive a this pointer. It has the same access rights as other member functions to the private members of the class.
As shown in Tony's example, the DataType::compare(const void *, const void *) will receive two pointers, each of which should point to an object of the DataType class. Since compare is a static class function, it can directly access the appropriate private members of DataType to determine the result.
If you have coded functions as operator: == and operator<, for which Tony shows the prototypes, then the comparison function does not have to be a member of the class. It could be a normal function such as:
int compare_DataType(const void *left, const void *right) { const DataType *dtLeft = (const DataType *)left; const DataType *dtRight = (const DataType *)right; return (*dtLeft < *dtRight) ? -1 : (*dtLeft == *dtRight) ? 0 : 1; }A good reason for keeping compare as a static class function is to show it as part of the class interface. The scoping of the name with DataType:: acts to differentiate between compare functions of two different classes. This scoping avoids having to use a name prefix or suffix to provide a class indication.If qsort is compiled as a C function, rather than a C++ function, then the problem of a mismatch between C and C++ calling sequences can occur, as covered in the original answer. Borland has found a way around this problem, but it is not part of the C++ language specification.
More on Pointers
QIn "caveats" ("Q&A," The C Users Journal, June 1994) you give an example:
void a_function(int *p_first, const int * p_second) { /* What you can do */ * p_first = *p_second; /* What you cannot do */ p_second = p_first; /* What you should not do */ p_first = p_second; }The problem is that the comment on the middle line is wrong, I assume the statement was meant to be:
/* What you cannot do */ *p_second = *p_first;Tony CookA
Thanks for your sharp-eyed correction. The example should have looked like this:
void a_function(int *p_first, const int * p_second) { /* What you can do */ *p_first = *p_second; /* What you cannot do */ *p_second = *p_first; /* What you should not do */ p_second = p_first; p_first: p_second; }Only the int which is being pointed to by the second parameter is a const. Therefore you cannot alter the value being pointed at, but you can change the value of the pointer itself.