• Tidak ada hasil yang ditemukan

Book Praise for Effective C++, Third Edition

N/A
N/A
Eben Ezer

Academic year: 2024

Membagikan "Book Praise for Effective C++, Third Edition"

Copied!
321
0
0

Teks penuh

The first edition of this book counts among the small (very small) number of books for which I have significantly improved my skills as a 'professional' software developer. PERSONALIZATION NOTICE: To discourage unauthorized use of this e-book and thereby allow its publication without DRM, each copy of the PDF version identifies its purchaser.

Constructors, Destructors, and

Designs and Declarations 78 Item 18: Make interfaces easy to use correctly and hard to

Inheritance and Object-Oriented Design 149 Item 32: Make sure public inheritance models “is-a.” 150

Templates and Generic Programming 199 Item 41: Understand implicit interfaces and compile-time

My understanding of Boost's review process (summarized in point 55) was refined by David Abrahams. Blind application of the Articles in this book is clearly inappropriate, but at the same time, you probably shouldn't violate any of the guidelines without a good reason.

View C++ as a federation of languages

This is the part of C++ to which the classic rules for object-oriented design apply most directly. The STL has special ways of doing things, and when you're working with the STL, you should be sure to follow its conventions.

Prefer consts, enums, and inlines to #defines

Once a macro is defined, it is in effect for the rest of the compilation (unless it is .. unprocessed somewhere along the line). Given the availability of consts, enums, and inlines, your need for the preprocessor (especially #define) is reduced, but not eliminated.

Use const whenever possible

Therefore, making a const member function call a non-const function is wrong: the object is subject to change. When const and non-const member functions have essentially identical implementations, code duplication can be avoided by having the non-const version call the const version.

Make sure that objects are initialized before they’re used

Again, the relative order of initialization of non-local static objects defined in different translation units is undefined. Finally, design around the initialization order uncertainty that affects non-local static objects defined in separate translation units.

Know what functions C++ silently writes and calls

NamedObject declares neither the copy constructor nor the copy assignment operator, so compilers will generate those functions (if they are needed). Finally, compilers reject implicit copy assignment operators in derived classes that inherit from base classes that declare the copy assignment operator private.

Explicitly disallow the use of compiler- generated functions you do not want

If you don't declare a copy constructor or a copy assignment operator, compilers can generate them for you. You'll find that both the copy constructor and the copy assignment operator are declared private and undefined in each case.

Declare destructors virtual in polymorphic base classes

Some classes are designed to be used as base classes, but are not designed to be used polymorphically. Classes that are not designed to be base classes or not designed to be used polymorphically should not declare virtual destructors.

Prevent exceptions from leaving destructors

Neither the standard string type, for example, nor the STL container types are even designed as base classes, much less polymorphic. If clients of a class must be able to react to exceptions thrown during an operation, the class must provide a normal (ie, nondestructive) function that performs the operation.

Never call virtual functions during construction or destruction

During base class construction of a derived class object, the type of object is that of the base. In our example, while the Transaction constructor runs to initialize the base class part of a BuyTransaction object, the object is of type Transaction.

Have assignment operators return a reference to *this

Using a helper function to create a value to pass to the base class constructor is often more convenient (and more readable) than going through the muck-ups on the member initialization list to give the base class what it needs. This is important because the fact that these data members will be in an undefined state is why virtual function calls during base class construction and destruction do not go to derived classes at all.

Handle assignment to self in operator=

If you are concerned about efficiency, you can put the identity test at the top of the function again. Ensure that any function that operates on more than one object is correct if two or more objects are the same.

Copy all parts of an object

PriorityCustomer's copy functions look like they are copying everything to PriorityCustomer, but look again. Regardless of the source, it's important to release it when you're done with it.

Use objects to manage resources

It is about the importance of using objects to manage resources. auto_ptr and tr1::shared_ptr are just examples of objects that do this. If you still think it would be nice to have auto_ptr- and tr1::shared_ptr-like classes for strings, check out Boost (see article 55).

Think carefully about copying behavior in resource-managing classes

Unfortunately, the default behavior of tr1::shared_ptr is to delete what it points to when the reference count goes to zero, and that's not what we want. But mutexPtr's destructor will automatically call tr1::shared_ptr's destructor - unlock, in this case - when the mutex's reference count goes to zero.

Provide access to raw resources in resource- managing classes

You need a way to convert an object of the RAII class (in this case, tr1::shared_ptr) to the raw resource it contains (eg the underlying Investment*). Because it is sometimes necessary to get at the raw resource inside a RAII object, some RAII class designers grease the chips by providing an implicit conversion function.

Use the same form in corresponding uses of new and delete

The rule is simple: if you use [] in a new expression, you must use [] in the corresponding delete expression. If you use [] in a new expression, you must use [] in the corresponding delete expression.

Store newed objects in smart pointers in standalone statements

The “new Widget” expression must be executed before the tr1::shared_ptr constructor can be called, because the result of the expression is passed as an argument to the tr1::shared_ptr constructor, but the call to priority can be done first and to be executed second, or third. In this revised code, the “new Widget” expression and the call to the tr1::shared_ptr constructor are in a separate statement than the one calling priority, so compilers are not allowed to transfer the call to priority between them move.

Make interfaces easy to use correctly and hard to use incorrectly

The createInvestment implementor can avoid such problems by returning tr1::shared_ptr with getRidOfInvestment bound to it as delete. The tr1::shared_ptr constructor insists that its first parameter is a pointer, and 0 is not a pointer, it is an int.

Treat class design as type design

If you want to allow only explicit conversions, you'll want to write functions to perform the conversions, but you'll need to avoid making them type conversion operators or non-explicit constructors that can be called with one argument. The answer to this question determines which functions you will declare for your class.

Prefer pass-by-reference-to-const to pass-by- value

The way around the slice problem is to pass w by referencing -const: void printNameAndDisplay(const Window& w) // fine, parameter won't do that. For everything else, follow the advice of this entry and prefer pass-by-reference-to-const to pass-by-value.

Don’t try to return a reference when you must return an object

If the function is to return a reference in the case of the operator, it must return a reference to some Rational object that already exists and contains the product of the two objects to be multiplied. No, if the * operator is to return a reference to such a number, it must itself create that number object.

Declare data members private

If you hide your data members from your clients (ie, encapsulate them), you can ensure that class invariants are always maintained because only member functions can affect them. Thus, protected data members are just as unencapsulated as public ones, because in both cases, if the data members are changed, an unknowingly large amount of client code is broken.

Prefer non-member non-friend functions to member functions

From an encapsulation point of view, the choice is not between member and non-member functions, it is between member functions and non-member non-friend functions. All they need to do is add more non-member non-friend functions to the namespace.

Declare non-member functions when type conversions should apply to all parameters

This leads to an important observation: the opposite of a member function is a non-member function, not a friend function. If you need type conversions on all parameters to a function (including what would otherwise be pointed to by this pointer), the function must be a non-member.

Consider support for a non-throwing swap

What we would like to do is tell std::swap that when Widgets are swapped, the way to perform the swap is to swap their internal pImpl pointers. Finally, if you call swap, make sure you use a declaration to make std::swap visible in your function, and then call swap without any namespace qualifiers.

Postpone variable definitions as long as possible

The encrypted object is not completely unused in this function, but it is unused if there is an exception. In many cases, the first thing you want to do with an object is to give it some value, often via a task.

Minimize casting

The above code does not call Window::onResize on the current object and then perform the SpecialWindow-specific operations on that object - it calls Window::onResize on a copy of the base class portion of the current object before performing the SpecialWindow-specific actions on the current object. You don't want to trick compilers into treating *this as a base class object; you will call the base class version of onResize on the current object.

Avoid returning “handles” to object internals

As we've seen, this can also lead to const member functions that can change the state of an object. However, upperLeft and lowerRight still return handles to an object's internals, and that can be problematic in other ways.

Strive for exception-safe code

Functions that provide the strong guarantee promise that if an exception is thrown, the state of the program remains unchanged. A strong warranty is highly desirable and should be offered when practical, but it is not practical 100% of the time.

Understand the ins and outs of inlining

Perhaps it is now clear why it is a no-brainer whether to enter the Constructor of Derived. You can inline and otherwise tweak your features until the cows come home, but it's wasted effort unless you focus on the right features.

Minimize compilation dependencies between files

Like clients of Handle classes, clients of interface classes do not need to be recompiled unless the interface of the interface class is changed. In addition, objects derived from the interface class must contain a virtual table pointer (see point 7 again).

Make sure public inheritance models “is-a.”

This doesn't say, "Penguins can't fly." This says, "Penguins can fly, but it's a mistake for them to actually try." The injunction, "Penguins cannot fly," can be enforced by compilers, but violations of the rule, "It is an error for penguins to attempt to fly," can only be detected at runtime.

Avoid hiding inherited names

This is why the above user declarations are in the public part of the derived class: names that are public in a base class should also be public in a public derived class.). A user declaration won't do the trick here, because a user declaration makes all inherited functions with a given name visible in the derived class.

Differentiate between inheritance of

That's the whole story of inheritance and name hiding, but when inheritance is combined with templates, a whole different kind of "inherited names are hidden" issue arises. Some functions should not be redefined in derived classes, and when they are, you need to say so by making those functions non-virtual.

Consider alternatives to virtual functions

If the advantages of using a function pointer instead of a virtual function (eg, the ability to have health calculation functions for each object and the ability to change such functions at runtime) offset the potential need for decreased GameCharacter encapsulation is something that should. set based on design. Alternatives to virtual functions include the NVI idiom and various forms of the Strategy design pattern.

Never redefine an inherited non-virtual function

Referensi

Dokumen terkait