We are going to spend some time looking at C++ libraries, starting with a couple of library utilities used for reporting errors. These are <cassert> and <cerrno>, and work the same in C++ as in C. We will be using the older <assert.h> and <errno.h>, because the newer names don't yet exist in many compilers.
assert.h defines a macro assert() used to check assertions within a program. For example:
#include <stdio.h> #include <assert.h> int main() { FILE* fp = fopen("test.txt", "r"); assert(fp != NULL); return 0; }
If the argument to assert() is false (zero), the program terminates by calling the library function abort(), and gives a diagnostic as to the file and line of the error. In this example, an error like:
Assertion failed: fp != NULL, file x2.c, line 8 Abnormal program termination
comes out. Note that we could shorten the test to:
assert(fp);
identical to fp != NULL.
assert() is useful for "should never happen" kinds of errors, or for quick prototyping. It's not really suitable as a primary tool for giving end-user error messages.
Another error-reporting tool is <cerrno>. This has antecedents in the UNIX operating system, where a system call would return -1 on failure, and set a global variable "errno" to a number giving a more precise indication of what failed.
An example of using this technique is:
#include <stdio.h> #include <errno.h> #include <iostream.h> #include <string.h> int main() { errno = 0; FILE* fp = fopen("test.txt", "r"); if (fp == NULL) cout << strerror(errno) << endl; return 0; }
errno is reset, and then an fopen() call made, which will ultimately invoke a system call open() to open a file. If fopen() returns NULL, errno can be queried to find out what exactly went wrong. strerror() is used to retrieve the text of the various error message codes represented by errno.
In this example, the output is:
No such file or directory
This mechanism is useful in obtaining detail about errors, but you need to be careful to reset errno each time. Also, errno is not thread-safe.
C++ inherits C-style strings from C, where a sequence of characters is terminated with a null character and referenced via a char* pointer, and storage for dynamic strings must be explicitly managed. For example:
#include <stdio.h> #include <string.h> int main() { char buf[25]; strcpy(buf, "testing"); printf("%s\n", buf); }
This approach works pretty well and is efficient, but is quite low-level and prone to errors.
A newer facility is C++-style strings. A simple example, that reads from standard input and writes each line to standard output, after reversing the characters in the line, looks like this:
#include <iostream> #include <string> using namespace std; int main() { string instr; while (cin >> instr) { string outstr = ""; for (int i = instr.length() - 1; i >= 0; i--) outstr += instr[i]; cout << outstr << endl; } return 0; }
Note in this example that >> and << are overloaded for I/O, that [] is used to index individual characters, that += is used to concatenate an individual character to a string, and that there's no need to worry about memory management.
The string class is based on a template "basic_string" that provides string operations, and is defined as:
typedef basic_string<char> string;
But you don't need to worry about this unless you really want to make sophisticated use of string facilities. Note also that string is defined in the "std" namespace, which must be included via a using declaration.
Strings have value semantics, meaning that a copy is done when one string is assigned to another. So, for example, the output of this program:
#include <iostream> #include <string> using namespace std; int main() { string s1 = "abc"; string s2 = s1; s1 = "def"; cout << s2 << endl; return 0; }
is "abc" and not "def".
Another example, illustrating some additional features of string, is one that replaces "abc" in input lines with "ABC", and writes the result to standard output:
#include <iostream> #include <string> #include <stdio> using namespace std; int main() { string str; while (cin >> str) { string::size_type st = str.find("abc"); if (st != string::npos) str.replace(st, 3, "ABC"); printf("%s\n", str.c_str()); } return 0; }
find() attempts to find a substring in the string, and returns its index if found. "string::npos" is a special value that indicates the search failed. Strings have the property:
length() < npos
If the search succeeds, we replace "abc" with "ABC". We then output the value using C-style I/O, as an illustration of a how a C++ string can be converted to a C one using c_str().
C++ strings offer a higher-level abstraction than C-style ones, and are preferred in most cases.
We've started looking at some of the features of the C++ standard library. One of these is numeric_limits, a template defined in <limits>. numeric_limits is used to obtain information about the properties of integral and floating-point types on the local system.
For example, this program:
#include <iostream> #include <limits> using namespace std; int main() { cout << numeric_limits<long>::digits << endl; cout << numeric_limits<double>::max_exponent10 << endl; return 0; }
prints "31" and "308" when using Borland C++ 5.0 on a PC. 31 is the number of non-sign digits in a long, and 308 the maximum base-10 exponent of a double. Properties that do not make sense for a type (such as max_exponent10 for int) are given default values.
The set of properties that is available will vary based on the underlying type. For example, floating-point types have information available on exponents, infinity, and so on.
Some of the common properties are illustrated by another example:
#include <iostream> #include <limits> using namespace std; int main() { cout << numeric_limits<short>::is_integer << endl; cout << numeric_limits<short>::min() << endl; cout << numeric_limits<short>::max() << endl; return 0; }
which checks whether the type (short) is an integral type, and obtains the minimum (-32768) and maximum (32767) values for the type.
If you define your own custom numeric type, it's a good idea to specialize numeric_limits for that type. For example, suppose that I have a type "LongLong" that is twice the length of a long. I might say something like:
#include <iostream> #include <limits> using namespace std; class LongLong {/* ... */}; class numeric_limits<LongLong> { public: inline static LongLong min() throw() {/* ... */} inline static LongLong max() throw() {/* ... */} }; int main() { cout << numeric_limits<LongLong>::min() << endl; cout << numeric_limits<LongLong>::max() << endl; return 0; }
and define the appropriate members suitable for this numeric type.
In previous issues we've occasionally mentioned that older versions of operator new() would return 0 if they failed, similar to what the C library function malloc() does. More recently, operator new() was specified as throwing an exception if memory could not be allocated.
In the C++ standard that was just finalized, both approaches are in fact supported. The standard operator new() throws a "bad_alloc" exception. But there's also a no-exception version, defined like this in <new>:
class bad_alloc : public exception {...}; struct nothrow_t {}; extern const nothrow_t nothrow; void* operator new(size_t) throw(bad_alloc); void* operator new(size_t, const nothrow_t&) throw(); ...
This says that two basic flavors of new() are supported (there are also other ones such as new[] for use with arrays). The first throws a bad_alloc exception if it can't allocate memory, while the second simply returns 0. The second flavor would be used like this:
int* ip = new (nothrow) int[10000]; // never throws an exception if (ip == 0) // allocation error
In other words, it's a syntactic variant of placement new() as described in issue #019.
This approach allows for error-handling strategies that do not use exceptions. Whether such strategies are "good" depends a lot on the particular application in question.
In previous issues we've acknowledged that there are a variety of ways in which existing C++ compilers treat issues like the declaration of main(), program termination, and so on. It is worth revisiting this topic, given the new standard for the language.
A C++ program starts by initializing objects with static storage duration, using zero/constant initialization (as in C, and known as "static initialization"), and then performing dynamic initialization (constructors and non-constant expression initialization) for objects in the order that the objects appear in a translation unit (order between translation units is undefined).
The function main() is then called. main() is a global function that must be declared as one of:
int main() int main(int argc, char* argv[]) // argc >= 0, argv[argc] == 0
main() cannot be predefined (such as in a library), overloaded, called within a program, inlined, or made static. The linkage of main() (for example C or C++ linkage) is implementation defined.
A "return X;" statement in main() results in the destruction of automatic objects, and works as if "exit(X)" was called. If execution falls off the end of main(), it will be treated as though a "return 0;" was the last statement.
The function abort() terminates a C++ program, without executing destructors for objects with automatic or static storage duration, and without calling functions registered with atexit(). atexit() is used to register functions that are to be called when the program terminates.
The effect of exit() is to call the destructors for all static objects, in the reverse order of their construction (automatic objects are not destructed). This last-in-first-out process also incorporates functions registered with atexit(), such that a function registered with atexit() after a static object is constructed, will be called before that static object is destructed.
After this process is complete, all open C streams are flushed and closed, files created with tmpfile() are removed, and an exit status is passed back to the calling system.
Some aspects of all the above are a little tricky, but it's important to understand the complete process of invocation and termination. Sometimes there are important parts of an application (such as stream I/O) that depend on the underlying mechanics of program startup and shutdown.
With the standardization of C++, there is a set of standard exceptions that have been identified. These are thrown in response to various types of error conditions, both in the standard library and in user programs.
The organization of these is:
exception bad_alloc (out of memory) bad_exception (called for unexpected exceptions) bad_cast (bad operand to dynamic_cast<>) bad_typeid (bad operand to typeid()) ios_base::failure (I/O output) logic_error length_error (invalid length) domain_error (domain error) out_of_range (argument out of range) invalid_argument (invalid argument) runtime_error range_error (out of range in internal computation) overflow_error (overflow error) underflow_error (underflow error)
arranged in a corresponding class hierarchy.
An example of using these exceptions would be the following program:
#include <iostream> #include <stdexcept> using namespace std; void f(int x, int y) { if (x < 0) throw invalid_argument("x < 0"); if (y < 0) throw invalid_argument("y < 0"); } int main() { try { f(37, -59); } catch (exception e) { cout << e.what() << endl; } return 0; }
The thrown exception is caught in main(). If it had not been, the program would terminate.
You can of course create your own exception classes, derived or not from "exception". But it's worth knowing about and using standard exceptions wherever possible.
The C++ standard library contains much support for containers and algorithms, I/O, locale support, and so on. One of the library classes (actually a template) that sort of falls between the cracks is pair<T1,T2>, defined in <utility>.
Pair is used to construct objects, which contain a pair of values of two arbitrary types T1 and T2. Pair is defined as:
template <class T1, class T2> struct std::pair { T1 first; T2 second; pair(const T1& x, const T2& y) : first(x), second(y) {} // omits a couple of other constructors }; A related function std::make_pair() is also provided to create pairs.
This is a very simple idea, but a quite useful one. For example, consider the problem of returning two values from a function, such as the minimum and maximum of a set of values:
#include <algorithm> #include <utility> #include <iostream> using namespace std; template <class T> pair<T,T> minmax(T* vec, int n) { return pair<T,T>(*min_element(vec, vec + n), *max_element(vec, vec + n)); } int main() { int vec[] = {1, 19, 2, 14, -5, 59, 67, -37, 100, 47}; pair<int,int> p = minmax(vec, 10); cout << p.first << " " << p.second << endl; return 0; }
minmax() takes a T* vector argument and a vector size, and returns the minimum and maximum values of the vector, using the library functions min_element() and max_element(). The values are passed back in a pair<T,T> structure.
Pair is used in the standard library, for example to represent the (key,value) pair within the map container.
A class for doing complex arithmetic is often presented as an illustration of how to design a user-defined type, and various versions of such a class exist today in actual C++ implementations.
The recently-standardized C++ library includes a complex type, as a template rather than simply a class. Basing complex on a template allows for specification of the underlying scalar type, with specializations provided for float, double, and long double. So three complex class types are guaranteed to be defined:
std::complex<float> std::complex<double> std::complex<long double>
All the usual operations on complex types are provided by this template. For example, a simple program that multiplies two complex numbers is:
#include <complex> #include <iostream> using namespace std; typedef complex<double> ComplexDouble; int main() { ComplexDouble a(1.0, 2.0); ComplexDouble b(3.0, 5.0); cout << a * b << endl; return 0; }
which takes the product of (1.0,2.0) and (3.0,5.0), yielding (-7.0,11.0).
Complex does not do any special error checking for domain or range errors, beyond that provided by underlying operations such as sqrt().
India seo freelance web designer India web development ecommerce website developer India
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100