A definition provides compilers with the details a declaration omits. For an object, the definition is where compilers set aside memory for the object. For a function or a function template, the definition pro- vides the code body. For ob- jects generated from structs and classes, initialization is performed by constructors. A default constructor is one that can be called without any arguments.
Constructors declared explicit are usually preferable to non-explicit ones, because they prevent compilers from performing unexpected often unintended type conversions. Unless I have a good reason for allowing a constructor to be used for implicit type conversions, I declare it explicit.
I encourage you to follow the same policy. Throughout this book, I use such highlighting to call your attention to material that is particularly noteworthy. Fortunately, copy construction is easy to distinguish from copy as- signment. The copy constructor is a particularly important function, because it defines how an object is passed by value.
For example, consider this: bool hasAcceptableQuality Widget w ; Widget aWidget; if hasAcceptableQuality aWidget The parameter w is passed to hasAcceptableQuality by value, so in the call above, aWidget is copied into w. Pass-by-reference-to-const is typically a better choice. For details, see Item Much of that related functionality has to do with function objects: objects that act like functions.
Such objects come from classes that overload operator , the function call operator. More likely is that the program will behave erratically, sometimes running normally, other times crash- ing, still other times producing incorrect results. In this book, I point out a number of places where you need to be on the look- out for it. Java and the. A client is someone or something that uses the code typically the in- terfaces you write.
The clients of a class or a template are the parts of the software that use the class or tem- plate, as well as the programmers who write and maintain that code.
When discussing clients, I typically focus on programmers, because programmers can be confused, misled, or annoyed by bad interfaces. After all, you are a client of the software other people develop.
In situa- tions where this is not the case, I distinguish among classes, func- tions, and the templates that give rise to classes and functions. When referring to constructors and destructors in code comments, I sometimes use the abbreviations ctor and dtor.
Naming Conventions I have tried to select meaningful names for objects, classes, functions, templates, etc. Two of my favorite parameter names, for example, are lhs and rhs. I often use them as parameter names for functions implementing binary operators, e.
For member functions, the left-hand argument is represented by the this pointer, so sometimes I use the parameter name rhs by itself. You may have noticed this in the declarations for some Widget member functions on page 5. Which reminds me. I often use the Widget class in examples. It has nothing to do with widgets in GUI toolkits. And yet they do.
Far from it. Each has an Item that describes it in some detail Item 54 for TR1, Item 55 for Boost , but, unfortunately, these Items are at the end of the book. I tried them in a number of other places. This entire book is about how to do that, but some things are more fundamental than others, and this chapter is about some of the most fundamental things of all.
As the language matured, it grew bolder and more adventurous, adopting ideas, features, and programming strategies different from those of C with Classes. Exceptions required different approaches to structuring functions see Item Templates gave rise to new ways of thinking about design see Item 41 , and the STL defined an approach to extensibility unlike any most people had ever seen.
How are we to make sense of such a language? Within a particular sublanguage, the rules tend to be simple, straightforward, and easy to remember. Blocks, statements, the preprocessor, built-in data types, arrays, pointers, etc. In fact, templates are so powerful, they give rise to a completely new programming paradigm, template metapro- gramming TMP. Its conventions regarding containers, iter- ators, algorithms, and function objects mesh beautifully, but tem- plates and libraries can be built around other ideas, too.
For example, pass-by-value is generally more efficient than pass-by-reference for built-in i. When you cross into the STL, however, you know that iterators and function objects are modeled on pointers in C, so for iterators and function objects in the STL, the old C pass-by-value rule applies again.
For all the details on choosing among parameter-pass- ing options, see Item Item 2: Prefer consts, enums, and inlines to defines. This can be confusing if you get an error during compilation involving the use of the constant, because the error mes- sage may refer to 1. In addition, in the case of a floating point constant such as in this example , use of the constant may yield smaller code than using a define.
The first is defining constant pointers. Because the initial value of class constants is provided where the constant is declared e. Of course, const data members can be encapsulated; NumTurns is. Older compilers may not accept the syntax above, because it used to be illegal to provide an initial value for a static class member at its point of declaration.
Furthermore, in-class initialization is allowed only for integral types and only for constants. The only exception is when you need the value of a class constant during compilation of the class, such as in the declaration of the array GamePlayer::scores above where compilers insist on knowing the size of the array during compilation. For more on enforcing design constraints through coding decisions, consult Item Like defines, enums never result in that kind of unnecessary memory allocation.
A second reason to know about the enum hack is purely pragmatic. Lots of code employs it, so you need to recognize it when you see it. In fact, the enum hack is a fundamental technique of template metapro- gramming see Item Whenever you write this kind of macro, you have to remember to parenthesize all the arguments in the macro body. Otherwise you can run into trouble when somebody calls the macro with an expression.
Furthermore, because callWithMax is a real function, it obeys scope and access rules. For example, it makes perfect sense to talk about an inline function that is private to a class. Item 3: Use const whenever possible. The wonderful thing about const is that it allows you to specify a semantic constraint — a particular object should not be modified — and compilers will enforce that constraint.
It allows you to communi- cate to both compilers and other programmers that a value should remain invariant. The const keyword is remarkably versatile.
Outside of classes, you can use it for constants at global or namespace scope see Item 2 , as well as for objects declared static at file, function, or block scope.
Inside classes, you can use it for both static and non-static data members. Others list it after the type but before the asterisk. Declaring an iterator const is like declaring a pointer const i. Having a function return a constant value is generally inappropriate, but sometimes doing so can reduce the incidence of client errors with- out giving up safety or efficiency.
Such code would be flat-out illegal if a and b were of a built-in type. One of the hallmarks of good user-defined types is that they avoid gra- tuitous incompatibilities with the built-ins see also Item 18 , and allowing assignments to the product of two numbers seems pretty gra- tuitous to me. Unless you need to be able to modify a parameter or local object, be sure to declare it const. Such member func- tions are important for two reasons.
First, they make the interface of a class easier to understand. Second, they make it possi- ble to work with const objects. That technique is viable only if there are const member functions with which to manipulate the resulting const-qualified objects.
The example of ctb above is artificial. What does it mean for a member function to be const? There are two prevailing notions: bitwise constness also known as physical constness and logical constness. That can lead to counterintuitive behavior. This leads to the notion of logical constness. Compilers disagree. They insist on bitwise constness. What to do? For example, suppose that operator[] in TextBlock and CTextBlock not only returned a reference to the appropriate character, it also performed bounds checking, logged access information, maybe even did data integrity validation.
Can you say code duplication, along with its attendant compi- lation time, maintenance, and code-bloat headaches? What you really want to do is implement operator[] functionality once and use it twice. That is, you want to have one version of operator[] call the other one. And that brings us to casting away constness. In this case, the const version of operator[] does exactly what the non-const version does, it just has a const-qualified return type.
Casting away the const on the return value is safe, in this case, because whoever called the non-const operator[] must have had a non- const object in the first place. So having the non-const operator[] call the const version is a safe way to avoid code duplication, even though it requires a cast.
Yes, we use a cast to add const! Technically, we do. A C-style cast would also work, but, as I explain in Item 27, such casts are rarely the right choice. The result may not win any beauty con- tests, but it has the desired effect of avoiding code duplication by implementing the non-const version of operator[] in terms of the const version.
Whether achieving that goal is worth the ungainly syntax is something only you can determine, but the technique of implementing a non-const member function in terms of its const twin is definitely worth knowing. Even more worth knowing is that trying to do things the other way around — avoiding duplication by having the const version call the non-const version — is not something you want to do.
Remember, a const member function promises never to change the logical state of its object, but a non-const member function makes no such promise. The reverse calling sequence — the one we used above — is safe: the non-const member function can do whatever it wants with an object, so calling a const member function imposes no risk. As I noted at the beginning of this Item, const is a wonderful thing.
Use it whenever you can. Reading uninitialized values yields undefined behavior. On some plat- forms, the mere act of reading an uninitialized value can halt your program. More typically, the result of the read will be semi-random bits, which will then pollute the object you read the bits into, eventu- ally leading to inscrutable program behavior and a lot of unpleasant debugging.
The best way to deal with this seemingly indeterminate state of affairs is to always initialize your objects before you use them. The rule there is simple: make sure that all constructors initialize everything in the object. Initialization took place earlier — when their default constructors were automatically called prior to entering the body of the ABEntry constructor.
The assignment-based version first called default constructors to initialize theName, theAddress, and thePhones, then promptly assigned new values on top of the default-constructed ones. All the work performed in those default constructions was therefore wasted.
The member initialization list approach avoids that problem, because the arguments in the initialization list are used as constructor arguments for the various data members.
In this case, theName is copy-constructed from name, theAddress is copy-con- structed from address, and thePhones is copy-constructed from phones. For most types, a single call to a copy constructor is more efficient — sometimes much more efficient — than a call to the default construc- tor followed by a call to the copy assignment operator.
Similarly, you can use the member initialization list even when you want to default-construct a data member; just specify nothing as an initialization argument. Because numTimesConsulted is of a built-in type, for example, leaving it off a member initialization list could open the door to undefined behavior.
Sometimes the initialization list must be used, even for built-in types. Many classes have multiple constructors, and each constructor has its own member initialization list. This approach can be especially helpful if the true initial values for the data members are to be read from a file or looked up in a database. In general, however, true member initial- ization via an initialization list is preferable to pseudo-initialization via assignment.
This order is always the same: base classes are ini- tialized before derived classes see also Item 12 , and within a class, data members are initialized in the order in which they are declared. That thing is — take a deep breath — the order of initialization of non-local static objects defined in different translation units. Stack and heap-based objects are thus excluded. Included are global objects, objects defined at namespace scope, objects declared static inside classes, objects declared static inside functions, and objects declared static at file scope.
Static objects are destroyed when the program exits, i. A translation unit is the source code giving rise to a single object file. And the actual problem is this: if ini- tialization of a non-local static object in one translation unit uses a non-local static object in a different translation unit, the object it uses could be uninitialized, because the relative order of initialization of non- local static objects defined in different translation units is undefined.
An example will help. Now suppose some client creates a class for directories in a file sys- tem. How can you be sure that tfs will be initialized before tempDir? Again, the relative order of initialization of non-local static objects defined in different translation units is undefined. There is a reason for this. Very hard.
Unsolvably hard. Fortunately, a small design change eliminates the problem entirely. These functions return refer- ences to the objects they contain. Clients then call the functions instead of referring to the objects. In other words, non-local static objects are replaced with local static objects.
Aficionados of design patterns will recognize this as a common implementation of the Sin- gleton pattern. An essential part of Singleton I ignore in this Item is preventing the creation of multiple objects of a particular type. That is, they use functions returning references to objects instead of using the objects themselves. The reference-returning functions dictated by this scheme are always simple: define and initialize a local static object on line 1, return it on line 2.
On the other hand, the fact that these functions contain static objects makes them prob- lematic in multithreaded systems. Then again, any kind of non-const static object — local or non-local — is trouble waiting to happen in the presence of multiple threads. One way to deal with such trouble is to manually invoke all the reference-returning functions during the sin- gle-threaded startup portion of the program.
This eliminates initializa- tion-related race conditions. If you steer clear of such pathological scenarios, however, the approach described here should serve you nicely, at least in single-threaded applications. First, manually initialize non-member objects of built-in types. Second, use member initialization lists to initialize all parts of an object. Finally, design around the initialization order uncertainty that afflicts non-local static objects defined in separate translation units.
Little wonder. In this chapter, I offer guidance on putting together the functions that comprise the backbone of well-formed classes. When is an empty class not an empty class?
Furthermore, if you declare no constructors at all, compil- ers will also declare a default constructor for you. All these functions will be both public and inline see Item As for the copy constructor and the copy assignment operator, the compiler-generated versions simply copy each non-static data mem- ber of the source object over to the target object.
This is important. NamedObject declares neither copy constructor nor copy assignment operator, so compilers will generate those functions if they are needed. The type of nameValue is string, and the standard string type has a copy constructor, so no2. Before the assignment, both p. How should the assignment affect p. After the assignment, should p.
Alternatively, should the string object to which p. Is that what the compiler-generated copy assignment operator should do? If you want to support copy assignment in a class containing a reference member, you must define the copy assignment operator yourself.
Compilers behave similarly for classes containing const members such as objectValue in the modified class above. Finally, compilers reject implicit copy assignment operators in derived classes that inherit from base classes declaring the copy assignment operator private.
Item 6: Explicitly disallow the use of compiler- generated functions you do not want. That being the case, the idea of making a copy of a HomeForSale object makes little sense. This puts you in a bind. Your class thus supports copying.
If, on the other hand, you do declare these functions, your class still supports copying. But the goal here is to prevent copying!
The key to the solution is that all the compiler generated functions are public. To prevent these functions from being generated, you must declare them yourself, but there is nothing that requires that you declare them public. Instead, declare the copy constructor and the copy assignment operator private. By declaring a member function explicitly, you prevent compilers from generating their own version, and by making the function private, you keep people from calling it.
Unless, that is, you are clever enough not to define them. As Item 12 explains, the compiler-generated versions of these functions will try to call their base class counterparts, and those calls will be rejected, because the copying operations are private in the base class. Multiple inheritance, in turn, can some- times disable the empty base class optimization again, see Item In general, you can ignore these subtleties and just use Uncopyable as shown, because it works precisely as advertised.
You can also use the version available at Boost see Item That class is named noncopy- able. Using a base class like Uncopyable is one way to do this. The problem is that getTimeKeeper returns a pointer to a derived class object e.
What typically happens at runtime is that the derived part of the object is never destroyed. If a call to getTimeKeeper happened to return a pointer to an AtomicClock object, the AtomicClock part of the object i. How- ever, the base class part i. This is an excellent way to leak resources, corrupt data structures, and spend a lot of time with a debugger. Eliminating the problem is simple: give the base class a virtual destructor.
Then deleting a derived class object will do exactly what you want. For example, TimeKeeper might have a virtual function, getCurrentTime, which would be implemented differently in the various derived classes. Any class with virtual functions should almost certainly have a virtual destructor.
If a class does not contain virtual functions, that often indicates it is not meant to be used as a base class. When a class is not intended to be a base class, making the destructor virtual is usually a bad idea. The implementation of virtual functions requires that objects carry information that can be used at runtime to determine which virtual functions should be invoked on the object.
The details of how virtual functions are implemented are unimpor- tant. What is important is that if the Point class contains a virtual function, objects of that type will increase in size.
No longer can Point objects fit in a bit register. As a result, it is no longer possible to pass Points to and from functions written in other languages unless you explicitly com- pensate for the vptr, which is itself an implementation detail and hence unportable. The bottom line is that gratuitously declaring all destructors virtual is just as wrong as never declaring them virtual. In fact, many people summarize the situation this way: declare a virtual destructor in a class if and only if that class contains at least one virtual function.
It is possible to get bitten by the non-virtual destructor problem even in the complete absence of virtual functions. The same analysis applies to any class lacking a virtual destructor, including all the STL container types e.
Occasionally it can be convenient to give a class a pure virtual destructor. Well, because an abstract class is intended to be used as a base class, and because a base class should have a virtual destructor, and because a pure virtual function yields an abstract class, the solution is simple: declare a pure virtual destructor in the class you want to be abstract. The rule for giving base classes virtual destructors applies only to polymorphic base classes — to base classes designed to allow the manipulation of derived class types through base class interfaces.
TimeKeeper is a polymorphic base class, because we expect to be able to manipulate AtomicClock and WaterClock objects, even if we have only TimeKeeper pointers to them.
Not all base classes are designed to be used polymorphically. Neither the standard string type, for example, nor the STL container types are designed to be base classes at all, much less polymorphic ones. Some classes are designed to be used as base classes, yet are not designed to be used polymorphically. If a class has any virtual functions, it should have a virtual destructor. Item 8: Prevent exceptions from leaving destructors.
With good reason. Suppose v has ten Widgets in it, and during destruction of the first one, an exception is thrown. But suppose that during those calls, a second Widget destructor throws an excep- tion. Depending on the precise conditions under which such pairs of simultaneously active exceptions arise, program execution either terminates or yields undefined behavior.
In this example, it yields undefined behavior. It would yield equally undefined behavior using any other standard library container e. Not that containers or arrays are required to get into trouble. Premature program termi- nation or undefined behavior can result from destructors emitting exceptions even without using containers and arrays.
There are two primary ways to avoid the trouble. It has the advan- tage that if allowing the exception to propagate from the destructor would lead to undefined behavior, this prevents that from happen- ing.
That is, calling abort may forestall undefined behavior. For this to be a viable option, the program must be able to reliably continue execution even after an error has been encountered and ignored. Neither of these approaches is especially appealing. The problem with both is that the program has no way to react to the condition that led to close throwing an exception in the first place. For example, DBConn could offer a close function itself, thus giving clients a chance to handle exceptions arising from that operation.
It could also keep track of whether its DBConnection had been closed, closing it itself in the destructor if not. That would prevent a connection from leaking. If an operation may fail by throwing an exception and there may be a need to handle that exception, the exception has to come from some non-destructor function. After all, they had first crack at dealing with the problem, and they chose not to use it. If functions called in a destructor may throw, the destructor should catch any exceptions, then swallow them or terminate the program.
Item 9: Never call virtual functions during construction or destruction. The last line of the Transaction constructor calls the virtual function logTransaction, but this is where the surprise comes in. During base class construction, virtual functions never go down into derived classes. Instead, the object behaves as if it were of the base type.
Because base class constructors execute before derived class con- structors, derived class data members have not been initialized when base class constructors run. If virtual functions called during base class construction went down to derived classes, the derived class functions would almost certainly refer to local data members, but those data members would not yet have been initialized.
That would be a non-stop ticket to undefined behavior and late-night debugging sessions. Not only do virtual functions resolve to the base class, but the parts of the language using runtime type information e. In our example, while the Transaction constructor is running to initialize the base class part of a BuyTransaction object, the object is of type Transaction. The same reasoning applies during destruction. The violation is so easy to see, some compilers issue a warning about it.
See Item 53 for a discussion of warnings. Even without such a warning, the problem would almost certainly become apparent before runtime, because the logTransaction function is pure virtual in Transaction. In this case, because logTransaction is pure virtual in Transaction, most runtime systems will abort the program when the pure virtual is called typically issuing a message to that effect.
The only way to avoid this problem is to make sure that none of your constructors or destructors call virtual functions on the object being created or destroyed and that all the functions they call obey the same constraint.
But how do you ensure that the proper version of logTransaction is called each time an object in the Transaction hierarchy is created? Clearly, calling a virtual function on the object from the Transaction constructor s is the wrong way to do it. There are different ways to approach this problem. One is to turn logTransaction into a non-virtual function in Transaction, then require that derived class constructors pass the necessary log information to the Transaction constructor.
That function can then safely call the non- virtual logTransaction. In this example, note the use of the private static function createL- ogString in BuyTransaction. Using a helper function to create a value to pass to a base class constructor is often more convenient and more readable than going through contortions in the member initialization list to give the base class what it needs.
How- ever, the convention is followed by all the built-in types as well as by all the types in or soon to be in — see Item 54 the standard library e. These less obvious assignments to self are the result of aliasing: having more than one way to refer to an object.
In general, code that operates on references or pointers to multiple objects of the same type needs to consider that the objects might be the same. When they are, the delete not only destroys the bitmap for the current object, it destroys the bitmap for rhs, too. At the end of the function, the Widget — which should not have been changed by the assignment to self — finds itself holding a pointer to a deleted object!
About the only safe thing you can do with them is spend lots of debugging energy figuring out where they came from. Item 29 explores exception safety in depth, but in this Item, it suffices to observe that in many cases, a careful ordering of statements can yield exception-safe and self-assignment- safe code. Even without the identity test, this code han- dles assignment to self, because we make a copy of the original bit- map, point to the copy we made, then delete the original bitmap. It may not be the most efficient way to handle self-assignment, but it does work.
It makes the code both source and object a bit bigger, and it introduces a branch into the flow of control, both of which can decrease runtime speed. The effectiveness of instruction prefetching, caching, and pipelining can be reduced, for example.
Techniques include comparing addresses of source and target objects, careful statement ordering, and copy-and-swap. Item Copy all parts of an object. In well-designed object-oriented systems that encapsulate the internal parts of objects, only two functions copy objects: the aptly named copy constructor and copy assignment operator.
Yet most compilers say nothing about this, not even at maximal warn- ing level see also Item The conclusion is obvious: if you add a data member to a class, you need to make sure that you update the copying functions, too.
If you forget, compilers are unlikely to remind you. One of the most insidious ways this issue can arise is through inherit- ance. Yes, they copy the data mem- ber that PriorityCustomer declares, but every PriorityCustomer also contains a copy of the data members it inherits from Customer, and those data members are not being copied at all!
Assuming it has one. That constructor will perform a default initialization for name and lastTransaction. Any time you take it upon yourself to write copying functions for a derived class, you must take care to also copy the base class parts.
In practice, the two copying functions will often have similar bodies, and this may tempt you to try to avoid code duplication by having one function call the other. Your desire to avoid code duplication is laud- able, but having one copying function call the other is the wrong way to achieve it.
Trying things the other way around — having the copy constructor call the copy assignment operator — is equally nonsensical. A con- structor initializes new objects, but an assignment operator applies only to objects that have already been initialized. Performing an assignment on an object under construction would mean doing some- thing to a not-yet-initialized object that makes sense only for an ini- tialized object.
Instead, if you find that your copy constructor and copy assignment operator have similar code bodies, eliminate the duplication by creat- ing a third member function that both call. Such a function is typi- cally private and is often named init. This strategy is a safe, proven way to eliminate code duplication in copy constructors and copy assignment operators.
Instead, put common functionality in a third function that both call. Other common resources include file descriptors, mutex locks, fonts and brushes in graphical user interfaces GUIs , database connections, and network sockets. Experience has shown that dis- ciplined adherence to this approach can all but eliminate resource management problems. The chapter then moves on to Items dedicated specifically to memory management.
These latter Items complement the more general Items that come earlier, because objects that man- age memory have to know how to do it properly. Item Use objects to manage resources.
If such a return were executed, control would never reach the delete statement. A similar situation would arise if the uses of createIn- vestment and delete were in a loop, and the loop was prematurely exited by a break or goto statement.
If so, control would again not get to the delete. Of course, careful programming could prevent these kinds of errors, but think about how the code might change over time.
To make sure that the resource returned by createInvestment is always released, we need to put that resource inside an object whose destruc- tor will automatically release the resource when control leaves f. Resource Management Item 13 63 Many resources are dynamically allocated on the heap, are used only within a single block or function, and should be released when control leaves that block or function. Sometimes acquired resources are assigned to resource-managing objects instead of initializing them, but ei- ther way, every resource is immediately turned over to a resource- managing object at the time the resource is acquired.
Because destructors are called auto- matically when an object is destroyed e. If there were, the object would be deleted more than once, and that would put your program on the fast track to undefined behavior. An RCSP is a smart pointer that keeps track of how many objects point to a particular resource and automatically deletes the resource when nobody is pointing to it any longer. As such, RCSPs offer behav- ior that is similar to that of garbage collection.
Item 16 describes the difference. Those considerations are the topic of Items 14 and Combatting that problem calls for an interface modification to createInvestment, a topic I address in Item Item Think carefully about copying behavior in resource-managing classes.
This is a specific example of a more general question, one that every RAII class author must confront: what should happen when an RAII object is copied? In many cases, it makes no sense to allow RAII objects to be copied. When copying makes no sense for an RAII class, you should prohibit it.
Item 6 explains how to do that: declare the copying operations private. In that case, copying the re- source-managing object should also copy the resource it wraps. Objects of such strings contain a pointer to the heap memory. When a string object is copied, a copy is made of both the pointer and the memory it points to. Such strings exhibit deep copying. The fix is simple, but the explanation behind it is too long to be added to this page.
Resource Management Item 15 69 to a raw resource and that when the RAII object is copied, owner- ship of the resource is transferred from the copied object to the copying object. Such versions are described in Item Item Provide access to raw resources in resource- managing classes.
Resource-managing classes are wonderful. But the world is not perfect. There are two general ways to do it: explicit conversion and implicit conversion. Because it is sometimes necessary to get at the raw resource inside an RAII object, some RAII class designers grease the skids by offering an implicit conversion function. That, in turn, would increase the chances of leaking fonts, the very thing the Font class is designed to prevent. For example, a client might accidently create a FontHandle when a Font was intended: Font f1 getFont ; For example, when f1 is destroyed, the font will be released, and f2 will dangle.
Sign up Log in. Web icon An illustration of a computer application window Wayback Machine Texts icon An illustration of an open book.
Books Video icon An illustration of two cells of a film strip. Video Audio icon An illustration of an audio speaker. Audio Software icon An illustration of a 3. Software Images icon An illustration of two photographs. Images Donate icon An illustration of a heart shape Donate Ellipses icon An illustration of text ellipses.
For this third edition, more than half the content is new, including added chapters on managing resources and using templates. Topics from the second edition have been extensively revised to reflect modern design considerations, including exceptions, design patterns, and multithreading.
Item 2: Prefer consts, enums, and inlines to defines. Item 3: Use const whenever possible. Goodreads helps you keep track of books you want to read. Want to Read saving…. Want to Read Currently Reading Read. Other editions. Enlarge cover. Error rating book. Refresh and try again.
Your email address will not be published.
0コメント