Item 11: Declare a copy constructor and an assignment operator for classes with dynamically allocated memory, which means you probably have pointers in your class.
copy constructor is used to initialize a new created object and assignment is used to initialize an already existing object.
Tricky: if you do not want others to use copy constructor and assignment operator, then just declare the functions but do not define them at all. Generally those two are enabled/disabled together.
Item 27: Explicitly disallow use of implicitly generated member functions you don't want.
Just make the functions private may not be enough, member and friend functions still can call them. But if we just declare them without definition, we will get link-time error when we try to invoke any of them.
Item 44: Say what you mean; understand what you're saying
public inheritance = isa
nonvirtual member function means invariance over specialization.
Item 45: Know what functions C++ silently writes and calls
class Empty {} is equal to
class Empty
{
Empty();
~Empty();
Empty(const Empty& rhs); //copy constructor
Empty& operator=(const Empty &); //assignment operator
Empty* operator&();
const Empty* operator&() const;
};
The compiler generated destructor is nonvirtual unless it's for a class inheriting from a base class that itself declares a virtual destructor.
c++ will refuse to generate the assignment operator for you if your class contain reference or const members. (if you think of the behaviors of assignment operator, you will see the reason)
Also c++ will refuse to generate the assignment operator for derived classes that inherit from base classes declaring the standard assignment operator private.
Item 14: Make sure base classes have virtual destructors
When you try to delete a derived class object through a base class pointer and the base class has a nonvirtual destructor, the results are undefined.
If a class does not contain any virtual functions, that is often an indication that it is not mean to be used as a base class. When a class is not intended to be used as a base class, making the destructor virtual is usually a bad idea.
One rule is declaring a virtual destructor in a class if and only if that class contains at least one virtual function. Also declare a pure virtual destructor in the class you want to be abstract. One twist: you must provide a definition for the pure virtual destructor, because this virtual destructor will definitely be called by the derived class's destructor.
Item 15: Have operator= return a reference to *this
C& C::operator=(const C &);
The return type of operator= must be acceptable as an input to the function itself, otherwise, it will prevent chains of assignments.
If we remove the const qualifier, it will prevent implicit type conversion. const-correctness: it's never legal to pass a const object to a function that fails to declare the corresponding parameter const.
Item16: Assign to all data members in operator=
Derived& Derived::operator=(const Derived & rhs)
{
if (this == &rhs) return * this;
Base::operator=(rhs); //make an explicit call to base class assignment operator= function
//*this will be still be the implicit left-hand object, just like calls to member functions within //other member functions.
y = rhs.y; // assign to data members defined in derived class
return *this;
}
One nastiest bugs in all c++-dom: it fails to copy the base class part when a derived object is copy constructed. To avoid this problem, derived's copy constructor must make sure that the Base's copy constructor is invoked instead of Base's default constructor. Just be sure to specify an initializer value for Base in the member initialization list of derived's copy constructor.
class Derived: public Base
{
//Base(rhs) will invoke base copy constructor
Derived(const Derived & rhs): Base(rhs), y(rhs.y) { }
}
Item17: Check for assignment to self in operator=
Aliasing: having two or more names for the same underlying object.
Object identity: 1) if the objects have the same value or 2) if the objects have the same address in memory or 3) write your own identity function and provide operator==.
Anytime you write a function in which aliasing could conceivably be present, you must take that possibility into account when you write the code.
Item 19: Understand the origin of temporary objects
True temporary objects in C++ are invisible and arise whenever a non-heap object is created but not named. Unnamed objects arise in one of the two situations: 1) when functions return objects and 2) when implicit type conversions are applied to make function calls succeed, these conversions occurs only when passing objects by value or when passing to a reference-to-const parameter.
Learn to look for such constructs, and your insight into the cost of "behind the scenes" compiler actions will markedly improve.
Item 20: Avoid data members in the public interface
Hide all your data members behind a wall of functional abstraction. If you implement access to a data member through a function, you can later replace the data member with a computation, and nobody using your class will be any the wiser.
Item 21: Use const whenever possible
1. Outside of classes, use it for global or namespace constants
const A * c = new A();
A* e = c;
This is wrong
A* const c = new A();
A* e = c;
This is right
2. Outside of classes, use it for static objects (local to a file or a block)
3. Inside of classes, use it for both static and nonstatic data members
const nonstatic data members are const to each independent class objects. If you want to have const data for the class, use enum. enum will not take objects' space and replaced during compilation time.
const members may only be initialized and must be initialized via initializer list, NEVER assigned. Also all the consturctors should initialize the const data members in their initializer list!
But how about const static?
4. For pointers, const char * const p = "hello";
5. Within a function declaration, const can refer to the function's return value, to individual parameters.
const int & foo (const int para) { }
6. For member functions, to the function as a whole.
6.1) Member functions differ only in their constness can be overloaded. The class object's constness decides which function will be invoked. const is part of the function signature, which mean you have to put const both in the class declaration and function definition if the definition is out of class body.
6.2) const member function can ONLY be called by const class object
6.3) constructor and destructor are exceptions and const class object could call constructor/destructor even they are not const.
6.4) a class object is constant after constructor and before destructor.
7. Mutable
bitwise constness vs conceptual constness (which shows constness only to the clients)
When applied to nonstatic data members, mutable frees those members from the constraints of bitwise constness. Mutable data members can NEVER be const, even the data members are in a const class object. Mutable data members can ALWAYS be updated, even in the const member function.
8. volatile
objects may be modified out of compiler's control, such as I/O interface data structure. Similar to const, volatile object can only invoke volatile member function, constructor and destructor.
Item 12: Prefer initialization to assignment in constructors
Construction of objects proceeds in two phases:
1. Initialization of data members
2. Execution of the body of the constructor that was called
Initialization is more efficient. We only need to call copy constructor for the data members if we use initialization list. But we have to call both default constructor and assignment operator= for the data member if we use assignment in constructors.
const and reference member can ONLY be initialized, NEVER assigned. So if we stick to initialization, we don't need to change initialization code if we later change some members to const or reference.
Item 13: List members in an initialization list in the order in which they are declared.
Rule: class members are initialized in the order of their declaration in the class. destructors for the members of an object are always called in the inverse order of their constructors. So in this way, compiler does not to keep track of the order in which the members were intialized for each object.
Only nonstatic data members are initialized according to the rule. Static data members act like global and namespace objects, so they are intialized only once.
Item 6: Use delete on pointer members in destructors
Adding a pointer member almost always requires each of the following:
1. Initialization of the pointer in EACH of the constructors. If no memory is to be allocated to the pointer in a particular constructor, the pointer should be initialized to 0.
2. Deletion of the existing memory and assignment of new memory in the assignment operator
3. Deletion of the pointer in the destructor
Item 22: Prefer pass-by-reference to pass-by-value
Pass by value: Function parameters are initialized with COPIES of the actual arguments, and function callers get back a COPY of the value returned by the function.
The meaning of passing an object by value is defined by the copy constructor of that object's class.
Pass-by-reference also avoids what is called the "slicing problem". When a derived class object is passed as a base class object, all the specialized features that make it behave like a derived class are "sliced" off, and you're left with a simple base class object, and you are left with a simple base class.
Item 23: Don't try to return a reference when you must return an object
const Rational operator*(const Rational & lhs, const Rational &rhs) { }
We can NOT return reference for the above function.
A function can only create a new object in only two ways: on the stack or on the heap. A reference is just a name, a name for some existing object.
Rational w, a, b, c;
w = a*b*c; //if we return references (to object created inside the function), memory leak
Item 29: Avoid returning "handles" to internal data
The lifetime of the temporary object returned from function will be until the end of the expression containing the call.
For const member functions, returning handles is ill-advised, because it violates abstraction. Even for non-const member functions, however, returning handles can lead to trouble, especially when temporary objects get involved. We should avoid dangling handles as we avoid dangling pointers.
Item 28: Partition the global namespace
A namespace is just a fancy way of letting you use the prefixes you know and love without making people look at them all the time.
Access symbols in namespace in any of the three ways:
1. importing all the symbols in a namespace into a scope: using namespace std;
2. importing individual symbols into a scope: using std::cout;
3. explicitly qualifying a symbol for one-time use: std::cout << "hello world!"; One of the nicest things about namespace is that potential ambiguity is not an error, provided you never refer to the conflict symbols.
Item 26: Guard again potential ambiguity
C++ Philosophy: potential ambiguity is not an error.
Access restrictions are not taken into account when disambiguating references to multiply inherited members. Becuase changing the accessibility of a class member should never change the meaning of a program.
Item 30: Avoid member functions that return non-const pointers or references to members less accessible than themselves.
You won't want to sacrifice the access restrictions that private and protected afford you. Use const whenever possible.
Item 31: Never return a reference to a local object or to a dereferenced pointer initialized by new within the function.
1. reference to non-existing object, which is undefined.
2. cause memory leak
Item 32: Postpone variable definitions as long as possible
In this way, you avoid not only constructing and destructing unneeded objects, you also avoid pointless default constructions. Define and initialize via copy constructor will make the meaning more clear.
Item 38: Never redefine an inherited default parameter value
Virtual functions are dynamically bound, but default parameter values are statically bound.
An object's static type is the type you declare it to have in the program text. An ojbect's dynamic type is determined by the type of the object to which it currently refers.
Item 37: Never redefine an inherited nonvirtual function
Nonvirtual functions are statically bound. Which nonvirtual function to call is decided by the declared type of the pointer.
class B { void f(); }
class D: public B {void f();}
B b;
D d;
B * pB = &b;
pB-> f();//call B::f()
pB = &d;
pB->f(); //call B::f()
Item 24: Choose carefully between function overloading and parameter defaulting
In general, if you can choose a reasonable default value and you want to employ only a single algorithm, you'll use default parameters.
You have to use overloaded functions if the algorithms depend on the input.
Using overloaded functions that call a common underlying function for some of their work to avoid code duplication.
Item 19: Differentiate among member functions, non-member functions, and friend functions
Virtual functions must be members. Non-member function can NOT be virtual. And if a function has to be dynamically bound, you've got to use a virtual function, a member of some class.
Operator >> and operator << style="font-weight: bold;">parameter list, never for the object on which a member function is invoked. In addition, if the function needs to access non-public members, make it a friend.
Everything else should be a member function.
Item 33: Use inlining judiciously
Inline is a hint, not a command.
Initially, don't inline anything, or at least limit your inlining to those functions that are truly trivial.
80-20, a typical program spends 80 percent of its time executing only 20 percent of its code. It's an important rule, because it reminds you that your goal as a software developer is to identify the 20 percent of your code that is actually capable of increasing your program's overall performance.
It's all wasted effort unless you're focusing on the right functions.
Item 39: Avoid casts down the inheritance hierarchy
Try to use virtual functions instead of if-then-else style of programmings that downcast objects.
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment