The C++ Standard Library Part 1
IntroductionMuch of the power and productivity of the C++ language comes from its standard library. However, the standard library is a fairly complicated collection of functions and classes, which can make it hard to use and hard to get the most out of. This article series aims to introduce basic usage and best practices for using the C++ standard library. As much as I will try to make each article only depend on the articles before hand, the standard library can be considered to be more or less an integrated whole, and as such I will be mentioning things that will be covered in later articles, and some things I will only partially cover and leave the details for later. Also, I will not be covering parts of the C++ standard library that have been incorporated from the C standard library, such as the C file I/O functions or the math functions. This first article focuses on some features of C++ important to the use of the standard library that may not familiar to beginning C++ programmers or programmers coming from another language like C. However, I will assume knowledge of the basic C++ language features like how to create and call functions, how to create classes, loops, if statements and using header files. [1] I also assume that you are familiar with the basics of using iostreams for input and output. Not necessarily anything fancy with iostreams, but the basics like using cout for output and fstreams to read numbers. In this article I will be going over namespaces, templates and exceptions. The coverage of these topics in this article is not meant to be comprehensive. Templates, for instance, are complicated enough that an entire book can be dedicated towards them (and has). However, the coverage should be sufficient to use most of the features of the C++ standard library. A Note About StandardsC++ is a constantly evolving language, so it's important to know what version a given article is talking about. The C++ language was originally created by Bjarne Stroustrup, and was released in a series of versions along with the compiler CFront. The C++ Standards committee was formed in 1989 and the first version of the standard was released in 1998 in the document ISO/IEC 14882:1998. In 2003, a technical corregenda was released in the form of the document ISO/IEC 14882:2003. This is an essentially an errata version, no major changes were introduced in 14882:2003, only clarifications and corrections. The standards committee is currently working on the next version of the standard, which is anticipated to be released in 2009 and will have major changes. In the meantime, the standard library committee is working on a series of additions to the library that are being released in a documents called technical reports. At this time, Technical Report 1 has been released, and Technical Report 2 is still in the process of accepting proposals. This article series is being written at the beginning of 2006, and so covers the C++ Standard with Technical Corregenda 1 (document ISO/IEC 14882:2003), and the C++ Standard Library committee's Technical Report 1. At this point, recent compilers are almost all mostly in compliance with ISO/IEC 14882:2003, but almost none of Technical Report 1. [2] However, many of the library additions detailed in Technical Report 1 are implemented in the Boost C++ Library. So while Boost is not part of the C++ Standard Library, I will be referring to parts of Boost in this article series as they form the basis of parts of the Standard Library not implemented by current C++ compilers. NamespacesOne trend in programming, and especially game programming, is the increased use of libraries from different sources. For example, a game might use a 3D rendering engine from Company A, a physics engine from Company B, a sound effects library from Company C and a networking library from Company D. On one hand, this is a good thing as it increased productivity; the game programmers can focus on making a game rather than implementing yet another 3D engine. On the other hand, the game programmer now needs to make sure that these different components work well together. One problem in getting components from different sources to work together is name collision. Name collision happens when there are two or more functions with the same name, or even two or more classes with the same name. This usually manifests as duplicate symbol linking errors. To continue the game example, the 3D rendering engine may have a function process_BSP() and the 3D audio library might also have a function called process_BSP(). To address this problem, C++ has namespaces. Namespaces are used to group identifiers together in named logical bundles. In the game example, the developer might try to put all the video functions in a video namespace and all the audio functions in an audio namespace. In this way, the function names will no longer collide. Putting things in a namespace is a lot like putting things into a class. For example, my personal code library lives in namespace aoi, so declaring things in that namespace can look like: namespace aoi { class MyClass { // stuff }; void my_function(void); } Of course the syntax of namespaces and classes aren't exactly the same. The namespace syntax is different from a class in that it doesn't need a semi-colon at the end of the closing brace (though putting one there doesn't hurt), and you can define the elements of a namespace over different files. // header one: namespace aoi { class MyFirstClass { }; } // header two: namespace aoi { class MySecondClass { }; } To access elements inside a namespace you use the :: operator much like you would use to get at static members in a class. aoi::MyClass my_object; aoi::my_function(); Things inside the aoi namespace don't need to use the aoi:: prefix. namespace aoi { class MyClass { public: MyClass(); // stuff }; void my_function(void); MyClass::MyClass() { my_function(); } } Also when writing code outside of the namespace, you don't need to use the :: operator on functions if the functions takes an argument defined in the same namespace. [3] namespace aoi { class MyClass { // stuff }; void another_function(const MyClass &); } // outside the namespace void some_other_function(void) { aoi::MyClass my_object; another_function(my_object); // doesn't need the aoi:: in front of another_function } This is usually transparent on the end of the person using the types in the namespace. However, this is a good reason to put non-member functions that work on a type in the same namespace as the type, especially overloaded operators. namespace aoi { namespace string { class sortable_string { // stuff }; bool operator<(const sortable_string & lhs, const sortable_string & rhs); // good } bool operator>(const string::sortable_string & lhs, const string::sortable_string & rhs); // bad } usingAnother way to access identifiers in a namespace is by a using declaration or a using directive. With a using declaration, you can specify an identifier in a namespace to be treated as part of the current scope. For example: namespace aoi { class MyClass { // stuff }; } // outside the namespace void some_other_function(void) { using aoi::MyClass; // You can use MyClass without the aoi:: for the rest of the function MyClass my_object; } MyClass global_object; // error: MyClass lives in namespace aoi and the using declaration was only // for the function's scope. A using directive is a lot like a using declaration, but it pulls everything from a given namespace into a scope. namespace aoi { class MyClass { // stuff }; void my_function(void); } using namespace aoi; // using directive brings everything from aoi into the current scope // which is the global scope in this case void some_other_function(void) { MyClass my_object; // neither of these need to use aoi:: to access the identifiers because my_function(); // they are in a scope that has a using namespace aoi directive } You can also use a using declaration in the global scope or a using directive in a function scope. What you can't do is put either in a class scope, as the using keyword means something different inside a class definition. [4] You also can't declare a namespace inside a class. struct SomeStruct { using aoi::MyClass; // illegal using namespace aoi; // also illegal namespace aoi { // this one too } void some_function(void) { using aoi::MyClass; // perfectly legal, lasts for the rest of the function } }; One thing that is legal, but you shouldn't do, is put a using directive in a header file, as it can introduce subtle and hard to track down errors related to name lookups. Also, when you don't explicitly put things inside a namespace, the identifier goes inside the global namespace, which you can access with operator:: with nothing before the ::. void some_function(void); // this declares a function in the global namespace namespace aoi { void some_function(void); // this declares a different function with the same name in // namespace aoi. void some_other_function(void) { some_function(); // calls aoi::some_function() aoi::some_function(); // also calls aoi::some_funciton() ::some_function(); // calls the first some_function() in the global namespace } } Also, even though you can't put a namespace in a class definition, you can put a namespace inside another namespace. namespace aoi { namespace memory { class SegmentManager; } } aoi::memory::SegmentManager sm1; // memory is nested inside aoi ::aoi::memory::SegmentManager sm2; // and aoi is nested inside the global namespace using namespace aoi; memory::SegmentManager sm3; // still need the memory:: prefix using memory::SegmentManager; // since there's a using namespace aoi, don't need to // prefix aoi:: here SegmentManager sm4; // all good However, using directives or declarations can introduce ambiguity. namespace aoi { void foo(void); } namespace jun { void foo(void); } using aoi::foo; using jun::foo; void bar(void) { foo(); // error, doesn't know if this should be aoi::foo() or jun::foo() } This, however, becomes reasonable with one small change: namespace aoi { void foo(void); } namespace jun { void foo(int); } using aoi::foo; using jun::foo; void bar(void) { foo(); // void aoi::foo(void); foo(1); // void jun::foo(int); } Here, because the two functions have different signatures, the compiler can resolve the ambiguity, much in the same way it can resolve the ambiguity from normal operator overloading. All the same, to prevent some subtle (and some not so subtle) errors, it's best to restrict using directives and declarations to the smallest needed scope. Finally, preprocessor definitions don't respect namespace scoping (or any other scoping). Because macros work via text substitution before the code ever reaches the compiler, a macro defined inside one namespace will still affect code outside that namespace with or without a using directive. namespace aoi { #define SILLY fred } int SILLY = 0; // creates a variable named fred, not SILLY This also means using declarations for macro names will probably not do what you expect, if it compiles at all. Namespaces and the Standard LibrarySo why are namespaces important for using the standard library? Most components of the standard library live in the namespace std. The classic "Hello World" program often looks like this: #include <iostream> int main(int, char **) { std::cout << "Hello World!" << std::endl; return 0; } Here cout and endl are parts of the standard library that live in namespace std. Of course, you can also use using directives and using declarations with namespace std. #include <iostream> using namespace std; int main(int, char **) { cout << "Hello World!" << endl; return 0; } #include <iostream> int main(int, char **) { using std::cout; using std::endl; cout << "Hello World!" << endl; return 0; } Though, for "Hello World", it's less typing just to put the std:: in front of cout and endl. One other thing to note is that a using directive, like using namespace std, will only bring in the parts of the namespace that have already been declared in the headers included so far. For example, if you've only included <iostream> then using namespace std won't magically import the declarations from the <vector> header [5]. Also, the guideline not to put a using directive in a header file goes double for the std namespace. Because almost everything in the standard library is in namespace std, and because the standard library is so large, if you have using namespace std in a header, the opportunities for the aforementioned strange and subtle errors is nearly unbounded. In particular, some older compilers have a hard time dealing with a using directive followed by more entries in to the namespace. // the moral equivalent of this may happen if you have a using directive in a header #include <iostream> using namespace std; #include <vector> vector<int> int_vector; // may not work properly Other important namespaces include std::tr1 and boost. std::tr1 is the namespace where the components specified in Technical Report 1 have been placed in the few compilers that current support them. boost is the namespace used by the Boost C++ library which contains many implementations of components specified in Technical Report 1. At this point in time it is more portable to use the boost namespace, but hopefully in the future, std::tr1 will be more useful. TemplatesTemplates are a C++ feature that allow you to write families of functions or types. One common example in game programming is the 3D vector class. For example, in one game, it might store vector information as floats most of the time since they take up less memory. However, in one part of the physics engine, it needs additional accuracy for the computations so it does everything with doubles. Without templates you might end up developing two vector classes and a lot of functions that were the same except for the type used. struct Vector3Float { float x; float y; float z; }; struct Vector3Double { double x; double y; double z; }; float dot_product(const Vector3Float & lhs, const Vector3Float & rhs) { return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; } double dot_product(const Vector3Double & lhs, const Vector3Double & rhs) { // exactly the same as the above except for the types it takes return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; } So instead of duplicating all that code for the different types, you can write class and function templates. template <typename T> struct Vector3 { T x; T y; T z; }; template <typename T> T dot_product(const Vector3<T> & lhs, const Vector3<T> & rhs) { return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; } The first definition defines a class template Vector3<>. Vector3<> can be instantiated with different types. For example, you can have a Vector3 of floats or a Vector3 of doubles. When you refer to a specific template instantiation, you put the template arguments in angle brackets after the template class' name. For example Vector3<float> or Vector3<double>. You can even have a Vector3<bool>, though that rarely makes sense, or a Vector3<long double>. The second definition defines a function template dot_product<>(). This function takes Vector3<> arguments that depend on what template argument the dot_product<>() function is instantiated with.
Vector3<float> a;
Vector3<float> b;
float dot = dot_product<float>(a, b);
One convenient thing about function templates is that if the compiler can figure out what the template arguments should be from the function arguments, then you don't need to explicitly specify the template argument for the function. Vector3<float> a; Vector3<float> b; float dot = dot_product(a, b); // compiler can figure out this should be dot_product<float>() // by itself However, there are cases where the compiler can't figure out what the template arguments should be. template <typename T> T multiply(T a, T b) { return a * b; } multiply(5, 5); // good; compiler knows that it should be multiply<int>() multiply(5, 4.0); // confusion; compiler can't decide between multiply<int>() // and multiply<double>() multiply<double>(5, 4.0); // good; explicit template argument When you declare a template type parameter you can use both the typename keyword and the class keyword. [6] template <typename T> struct Vector3 { T x; T y; T z; }; // means exactly the same thing template <class T> struct Vector3 { T x; T y; T z; }; Template arguments don't need to be primitives like float or double, they can also be class types, or even template instantiations. class Rational { // implements rational numbers }; Rational operator*(const Rational & lhs, const Rational & rhs) { // stuff } Rational operator+(const Rational & lhs, const Rational & rhs) { // stuff } Vector3<Rational> a; // a vector of rational numbers Rational dot_a = dot_product(a, a); // happily calculates the dot product in terms of Rationals Vector3<Vector3<float> > b; // I don't know what this would mean, but you can do it Vector3<float> dot_b = dot_product(b, b); // but this will blow up since Vector3<float> doesn't // define either binary operator * or operator+ // which are both used to calculate the dot product One note about syntax: when instantiating a template with a template instantiation, you should put a space between the angle brackets at the end of the declaration. In the above example, you need a space between the > for the Vector3<float> and the closing > for the Vector3<Vector3<float> >. If you leave the space in, the compiler will interpret the brackets as operator >> instead, which will make the declaration illegal. [7] A class template can have virtual functions and you can define member function templates for both class templates and normal classes. However, you cannot have a templated virtual function. struct BaseClass { virtual void update(void); }; template <typename T> struct TemplateClass : BaseClass { virtual void update(void); // OK }; struct NonTemplateClass { template <typename T> void twiddle(T t); // OK template <typename T> virtual void twaddle(T t); // Error }; Also, the compiler often can only do limited checking on the body of a class or function template until it is instantiated. With some compilers, the best they can do is detect unbalanced parenthesis or brackets inside function definitions. For example, MSVC 7.1 will let this code compile unless you try to instantiate it: template <typename T> T function(const T & a, const T & b) { return a -- b; } Dependent NamesInside the definition of a template class or function, there are some identifiers that are called dependent names. Basically anything that depends on the template parameters is a dependent name. So if the template parameter list looks like template <typename T, typename U> then T and U are dependent names. template <typename T> void some_function(const T & t) { T temp = 0; // T is a dependent name, but so is temp Vector3<T> vec; // Both Vector3<T> and vec are dependent names temp += vec.x * vec.x; // vec.x is a dependent name since it depends on vec Vector3<float> fred; // no dependent names here foo(); // nor here } Names that refer to types are called dependent types. In the above example, T and Vector3<T> are dependent types. Other dependent names are called type dependent expressions. temp, vecand vec.x are all type dependent expressions. In most cases, it's easy for the compiler to determine if something should be a dependent type or a type dependent expression. However, there is one place where there is an ambiguity: if the :: operator is used on a dependent type. In this case it could be either a nested type or typedef or it could be a static member variable. In this case, the compiler will assume that the name refers to a static member variable unless you tell it that it should be a type name. This is done with the typename keyword. In this case, you cannot substitute the class keyword. template <typename T> void function1(T t) { typename T::some_type variable; // T::some_type interpreted as a nested type variable = T::some_value; // no typename keyword, T::some_value is interpreted as // a static member variable } template <typename T> struct Vector3 { // stuff typedef T member_type; }; void function2(void) { Vector3<float>::member_type some_variable; // no typename needed, not a template function } template <typename T> void function3(void) { Vector3<float>::member_type some_variable; // no typename needed, Vector3<float> // doesn't depend on T Vector3<T>::member_type some_other_variable; // error: need typename because // Vector3<T> depends on T typename Vector3<T>::member_type yet_another_variable; // good; typename keyword used } template <class T> class AnotherTemplateClass { Vector3<double>::member_type some_variable; // good, Vector3<double> doesn't depend on T typename Vector3<T>::member_type another_variable; // also good, typename used and Vector3<T> // depends on T }; The difference between dependent names and non-dependent names shows up in another different way: non-dependent names are looked up when the template is defined, and dependent names are looked up when the template is instantiated. void foo(int bar); template <typename T> void function(T t) { foo(t); // not a dependent name so it always refers to the // foo(int) above (assuming that no other foo()s // have been defined previously) } void foo(double bar); void other_function(void) { foo(1); // calls foo(int) foo(1.0); // calls foo(double) function(1); // calls foo(int) function(1.0); // also calls foo(int) } This rule was introduced so that code between the point of the template definition and the point of instantiation won't accidentally change the meaning of a template. However, it has one nasty side effect: member function calls to base classes may not be looked up properly. template <typename T> struct A { void foo(void); }; template <typename T> struct B : A<T> { void bar(void) { foo(); // may cause a compiler error } }; Inside B<T>::bar() foo() appears to be a non-dependent name, so it's looked up as a global function. If no global function is found, then the compiler may generate an error. [8] In order to get the compiler to search for the name at the point of instantiation, when the base class will be resolved, you need to get foo() to look like a dependent name. The easiest method is to replace the call to foo() with this->foo(). Since the class is a instance of a class template, this is a dependent name, and thus so is this->foo(). CompilationOne other tricky detail about templates is that class templates and function templates are not actual classes or functions. Only specific instances are classes and functions. This means, for example, you can't create a pointer to a function template. And because they aren't actual classes or functions, the compiler doesn't generate code for the class or function template, it only generates code for the specific instantiations. This means that even though you can separate templates function declarations from their definitions, you usually can't put the definitions in a separate source file. // header file template <typename T> struct SomeClass { void some_member_function(void); }; //--------------------------------- // some_file.cpp SomeClass<int> s; s.some_member_function(); //--------------------------------- // some_other_file.cpp template <typename T> void SomeClass<T>::some_member_function(void) { // do something } This will most likely cause unresolved symbol linker errors. Basically the problem is that in some_file.cpp the compiler says it needs the definition for SomeClass<int>::some_member_function(). However, in some_other_file.cpp when the function is defined, the compiler doesn't know it needs to create a instantiation of that function for int. There are three ways to deal with the problem. The first way is to put the definition of the function in the header with the template declaration, or in some other inline file that the header includes. // header file template <typename T> struct SomeClass { void some_member_function(void); }; #include "some_class.inl" //--------------------------------- // some_file.cpp SomeClass<int> s; s.some_member_function(); //--------------------------------- // some_class.inl template <typename T> void SomeClass<T>::some_member_function(void) { // do something } This allows the compiler to generate code for the function calls in much the same way that inline functions work. [9] The second way is called explicit template instantiation. With this method, you keep the template function definitions in a separate source file, but then you tell the compiler which template instantiations you need. // header file template <typename T> struct SomeClass { void some_member_function(void); }; template <typename T> T another_function(T t); //--------------------------------- // some_file.cpp SomeClass<int> s; s.some_member_function(); another_function(1.0f); //--------------------------------- // some_other_file.cpp template <typename T> void SomeClass<T>::some_member_function(void) { // do something } template <typename T> T another_function(T t) { return t + t; } template SomeClass<int>; // explicit template instantiation for a class template float another_function<float>(float); // for a function Of course this means that every time a different template instantiation is require you'll need to modify the source file with the definitions in it. The third way is to use the export keyword. Unfortunately, the export keyword is implemented in very few compilers, so chances are you won't have the opportunity to use it. [10] Templates and the Standard LibraryTemplates are important to the standard library since the vast majority of the C++ standard library consists of class and function templates. For a simple example, take std::swap<>(). std::swap<>() is a function template who's job is to swap the value of two objects. One implementation of std::swap<>() looks like: template <typename T> void swap(T & a, T & b) { T temp = a; a = b; b = temp; } Also, some parts of the standard library that look like non-templated classes are actually typedefs for specific instantiations of class templates. For example, std::string is actually a typedef for std::basic_string<char>. One interesting side effect of this is that it means that most of the source code of the standard library is available to you in the headers. Usually it's hard to read, but most of it is there for you to see how things are implemented. ExceptionsOne problem in programming is how to signal and handle error conditions. One method is to use return values of functions to signal errors. However, one problem with this is that if the code calling the function doesn't know how to handle the error, it needs to propagate the error somewhere else. Another problem is that there are some places that error codes can't be used. Constructors, for instance, have no return value. To address these issues, C++ has exceptions. [11] Exceptions are one of the many topics in C++ that are at the same time both simple and hideously complicated. The concept itself is fairly simple. Instead of returning an error code when something goes wrong, you can instead throw an exception. The syntax to do so is correspondingly simple. void * ptr = some_function_returning_a_pointer(); if (!ptr) throw SomeExceptionType("Out of memory"); This causes the function to stop execution and start destroying everything on the stack until it finds something that can handle the exception. try { function_that_had_the_previous_code(); } catch (const SomeExceptionType & ex) { std::cerr << ex.what() << std::endl; } This code essentially says: I'm about to execute code that might throw an exception (the try block). If an exception is thrown, then I'll handle exceptions that I know about, in this case SomeExceptionType. If I get an exception that I know how to deal with, execute the code in the catch block. You can throw almost any C++ type, like character literals, integers, or class types. This is one of the instances in C++ that you shouldn't take advantage of the flexibility given to you. Throwing character literals, integers, floating point numbers or other primitive types is bad practice. When throwing exceptions you should throw exceptions of class types, and throw them by value. throw SomeExceptionType("Some Error String"); // good SomeExceptionType stack_variable("Some Error String"); throw &stack_variable; // bad; throwing local variable address throw new SomeExceptionType("Some Error String"); // bad; throwing newly allocated object throw "Some Error String"; // bad; throwing a string literal When you catch an exception you should catch by reference. [12] catch (SomeExceptionType & e) // good catch (SomeExceptionType e) // bad; catching by value It's also possible to have multiple catch blocks for different exception types and it's possible to have an catch block that will catch any exceptions thrown in the try block. try { // some code that might throw exceptions } catch(SomeExceptionType & e) { // handles exceptions of type SomeExceptionType } catch(...) { // handles any other exceptions } The catch blocks are tried in order. So having catch(...) before any other catch block makes the other catch blocks pointless. If you catch multiple types of exceptions then the exception types should come in the order of most derived exceptions before the more general exceptions. class DerivedType : public BaseType { // stuff }; // good try { } catch (DerivedType & e) { } catch (BaseType & e) { } catch (...) { } // bad try { } catch (BaseType & e) { } catch (DerivedType & e) { // will never catch anything since BaseType handler will // handle DerivedTypes too } catch (...) { } catch (SomeOtherType & e) { // will never catch anything since (...) traps all exceptions } Of course, knowing how to write a catch block is not very useful unless you know what to put in the catch block. In general, one of two things will be true. Either you don't know how to handle the exception, but you need to clean up resource or you have an idea what the problem is and how to handle it. In the second case you'll generally be the one who threw the exception, so you know how best to deal with it. For example, if you throw an out of memory condition, you might disable optional features and try to re-run the operation. (But see set_new_handler() later in this series.) But first, let's see what I mean by cleaning up resources. So when an exception occurs, the application goes into a meltdown mode. First, it takes the exception thrown, and copies the exception into a safe area. Then the stack unwinds, which means that everything on the stack is destroyed in the reverse order of their creation. void function_that_throws(void) { SomeObject obj1; throw MyException("Whoops."); } void function_with_try(void) { SomeObject obj2; try { SomeObject obj3; SomeObject * obj4 = new SomeObject(); function_that_throws(); SomeObject obj5; } catch (MyException & e) { std::cerr << e.what() << std::endl; } } In the above code, when the exception is thrown, obj1 is destroyed, then obj3 is destroyed. obj5 hasn't been created yet, so will not be destroyed. The object pointed to by obj4 will not be destroyed since it is not on the stack. obj2 won't be destroyed because it is outside the try/catch blocks. It would be destroyed if, instead of a MyException, some other exception was thrown, since the catch block wouldn't handle that. In any case, this code demonstrates a memory leak. The object pointed to by obj4 is not deleted, and there's no way to get at the object since all pointers to it are lost when the exception is thrown. So one way to write non-leaking code when you dynamically allocate something via new is to do something like: SomeObject * obj = new SomeObject(); try { // do stuff that might throw an exception delete obj; } catch (...) { delete obj; throw; } // done with obj, continue doing stuff There are some things to note about this code. The first thing is that in the catch(...) block, there's a throw all by itself. This rethrows the same object that was caught by the catch block. In this case, I've handled the exception by cleaning up the obj pointer, and I don't know how to actually deal with this error, so I'm going to punt and hope that some enclosing catch block knows how to deal with things. The other thing to notice is that this is really ugly code. Keep in mind that destructors of objects are called when the stack is unwound. So if we instead had a class that held the pointer instead of just a normal pointer, and the class destroyed the pointer automatically, we wouldn't need the try/catch blocks. The standard library does supply such a class, called std::auto_ptr<>. std::auto_ptr<SomeObject> obj(new SomeObject()); // do stuff that might throw an exception obj->some_function(); obj.reset(); This code creates a new SomeObject and gives ownership of the object to a std::auto_ptr<SomeObject>. If there's an exception, the auto_ptr will have it's destructor called, which in turn will call the delete on the pointer it was given. You can use operator -> on the auto_ptr to get at the members of the object just like a normal pointer, and the auto_ptr has member functions of its own like reset(), which deletes the object held by the auto_ptr. std::auto_ptr<> and classes like it are called smart pointers; they act like pointers, but have a little more intelligence built in. However, as smart pointers go, std::auto_ptr<> isn't very smart because all it can do is call delete. For example, you can't use it to hold dynamic arrays allocated with operator new[], because you should use operator delete[] instead of operator delete with dynamic arrays. Instead, of trying to use std::auto_ptr<> with dynamic arrays, you should use a std::vector<> (which I cover in the next article). You also can't use std::auto_ptr<> with something like the FILE * returned by C file I/O function fopen(), since you need to call fclose() and not delete. In this case, the C++ standard library has the classes, std::fstream, std::ofstream and std::ifstream. I'll cover all these classes in more detail later, but the general concept embodied by all these classes are the same: an object acquires a resource when it is created, and handles releasing the resource when it is destroyed. This technique is referred to resource acquisition is initialization or RAII. RAII is a very useful technique in C++ and not just for exception safety reasons. You can also specify a function will not throw an exception. [13] // will not throw any exception void function(void) throw() { // stuff } Generally, however, you will only want to do this if you are trying to implement a function or interface that requires a no-throw function, such as some custom allocator functions. Exceptions are the primary method the C++ standard library uses to signal error conditions and deal with error conditions from your code, and the C++ language itself will throw exceptions in various situations. As such the standard library includes a set of standard exceptions. The <exception> header defines the exceptions std::exception, which is the base class for all exceptions in the standard library, and std::bad_exception, which is an exception class used with exception specifications. The <typeinfo> header defines std::bad_cast, which is thrown if a dynamic_cast on reference types fails, and std::bad_typeid which is thrown if a typeid operation fails (such as when passed a null pointer). The <new> header contains std::bad_alloc which is thrown if operator new or operator new[] fails. Finally, <stdexcept> defines a number of exception classes, some of which are thrown by standard library classes or functions. std::logic_error std::domain_error std::invalid_argument std::length_error std::out_of_range std::runtime_error std::range_error std::overflow_error std::underflow_error Generally, it's good practice to derive your own exception classes from the standard library exception classes, and in particular from one of the classes in <stdexcept>. std::logic_error and its children std::domain_error, std::invalid_argument, std::length_error and std::out_of_range are used for errors on the part of the programmer. For example, supplying an index that is out of the range of valid indices. Often these situations are instead handled by assertions. However, they can be useful, for example, when interfacing with scripting languages. std::runtime_error and its children, std::range_error, std::overflow_error and std::underflow_error are useful when dealing with potential problems that are not necessarily the result of programmer error. Closing and ReferencesTemplates and exceptions are both topics that deserve much more room than I've allocated in this article. For more details on templates, you may wish to consult The C++ Programming Language, 3rd Edition by Bjarne Stroustrup or C++ Templates - The Complete Guide by David Vandevoorde and Nicolai Josuttis. Also, the C++ FAQ Lite contains many useful insights into specific issues in template programming. For more details on exceptions, you may want to consult again The C++ Programming Language, 3rd Edition by Bjarne Stroustrup, and especially the hard cover version of the book which has an additional section on exception handling in the end. Other good books for learning about exception handling include Exceptional C++ and More Exceptional C+ by Herb Sutter, and C++ Gotchas by Steven Dewhurst. Again, the C++ FAQ Lite covers many common issues about exception handling. There are a few details missing from my coverage about namespaces, which again can be found in The C++ Programming Language, 3rd Edition by Bjarne Stroustrup. General references for the standard library as a whole include yet again, The C++ Programming Language, 3rd Edition by Bjarne Stroustrup and also The C++ Standard Library by Nicolai Josuttis and of course the actual text of the C++ Standard: ISO/IEC 14882:2003, which can be found either as hardcover as The C++ Standard: Incorporating Technical Corrigendum No. 1 from Wiley or from your national standards body. For example, for the United States, it can be found both printed and for electronic download (for a fee) from ANSI. [14] You can visit the C++ Standard Committee's website here. In particular you can download draft copies of the C++ Standard there, which, while they are not up to date, are free. This includes the TR1 paper. Generally speaking the differences between the draft and final versions of the papers are minor. Also, I will also be referring to the Boost project quite a bit as well. If you are interested in the pre-standards history of C++ (including much of what makes up the modern standard library) consider reading The Design and Evolution of C++ by Bjarne Stroustrup, or The Annotated C++ Reference Manual by Margaret Ellis and Bjarne Stroustrup. A different approach to introducing the standard library, and the C++ language as a whole, is taken by the book Accelerated C++ by Andrew Koenig and Barbara Moo. If the approach taken in this article series doesn't work for you, you may want to try reading that book. In the next article I'll cover one of the more useful of the standard library classes: std::vector. 1) If you aren't that comfortable with header files you may wish to read this article. 2) To be pedantic, Technical Report 1 is non-normative, so compiler vendors are not under any obligation to provide the library features detailed in Technical Report 1. 3) This is called Koenig lookup. 4) However, you can use using directives and declarations in a class member function's implementation. 5) Unless, for some bizarre reason, your compiler's <iostream> header directly or indirectly includes <vector>. 6) Many people use the convention of using typename when the template argument can be any type including primitives, and using class when the template argument must be a class. An exception is that template-template parameters require the class keyword. However, template-template parameters are outside the scope of this article as the C++ Standard Library does not use them. 7) This is something that will probably change in the next version of the C++ Standard. 8) Not compilers will. However, the ones that don't tend to be older compilers that look up all names at the point on instantiation. 9) It also generates the same headaches for the linker that inline functions do. 10) Also, the export keyword doesn't work like you might expect it would as it doesn't allow for true seperate compilation of templates. For more details see these articles: "Export" Restrictions, Part 1 and "Export" Restrictions, Part 2. 11) And you can't get away from them. The language itself will throw exceptions in many cases. For example, if memory allocation fails in a new expression, an exception will be thrown. If a dynamic_cast to a reference type fails an exception will be thrown. 12) Catching by value can lead to problems such as slicing, where you lose information about the exception being caught. Another issue is that sometimes catching by value may invoke a copy constructor that itself may throw an exception. Throwing an exception while doing exception handling is a bad thing. 13) Actually, you can also specify that a function will only throw certain exceptions. // will only throw SomeException and SomeOtherException void function1(void) throw(SomeException, SomeOtherException) { // stuff } Exception specifications were introduced with the hope that it would allow certain optimizations. However, the way that exception specifications were defined in the standard generally makes them perform worse than functions without an exception specification. Also, even now, many compilers do not actually implement exceptions specifications other than throw(). At this point, I would recommend not to use them at all, including not using throw(). (Again, unless it's required by an interface.) 14) However, the hard cover version from Wiley is much less expensive than the version from ANSI. Discuss this article in the forums
See Also: © 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
|