C++ storage-class specifiers
auto: default
register: stored in machines registers
static: stored in global static section
extern: declaration only, defined somewhere else
mutable: class member variable only, could be modified by const member functions
volatile: value could be modified by other thread, tell compiler don’t over-optimizate
const: constant
C++ casting operator
var=(type)expr: C-style
dynamic_cast: polymorphic types
static_cast: non-polymorphic types
const_cast: remove const
reinterpret_cast: simple reinterpretation of bits.
Effective C++ v3
Effective C++ Item 3: Use const whenever possible
char greeting[] = "Hello";
char *p = greeting; // non-const pointer, non-const data
const char *p = greeting; // non-const pointer, const data
char const *p = greeting; // the same as const char *p = greeting;
char * const p = greeting; // const pointer, non-const data
const char * const p = greeting; // const pointer, const data
The purpose of const on member functions is to identify which member functions may be invoked on const objects
mutable frees non-static data members from the constraints of bitwise constness:
Item 4: Make sure that objects are initialized before they're used
C++ initialization sequence:
constructor init list before constructor body
base classes are initialized before derived classes
and within a class, data members are initialized in the order in which they are declared
Sometimes the initialization list must be used, even for built-in types. For example,
data members that are const or are references must be initialized; they can't be assigned
the relative order of initialization of non-local static objects defined in different translation units is undefined.
non-local static objects are replaced with local static objects
class FileSystem { ... }; // as before
FileSystem& tfs() // this replaces the tfs object; it could be
{ // static in the FileSystem class
static FileSystem fs; // define and initialize a local static object
return fs; // return a reference to it
}
Item 5: Know what functions C++ silently writes and calls
//compiler generate functions for a empty class
class Empty {
public:
Empty() { ... } // default constructor
Empty(const Empty& rhs) { ... } // copy constructor
~Empty() { ... } // destructor — see below for whether it's virtual
Empty& operator=(const Empty& rhs) { ... } // copy assignment operator
};
It means that if you've carefully engineered a class to require constructor arguments,
you don't have to worry about compilers overriding your decision by blithely adding a constructor that takes no arguments
class contain reference or const member variables can not use default assignment operator
Item 7: Declare destructors virtual in polymorphic base classes
C++ specifies that when a derived class object is deleted through a pointer to a base class with a non-virtual destructor,
results are undefined. What typically happens at runtime is that the derived part of the object is never destroyed.
Item 8: Prevent exceptions from leaving destructors
class Widget {
public:
...
~Widget() { ... } // assume this might emit an exception
};
void doSomething()
{
std::vector<Widget> v;
...
} // v is automatically destroyed here
When the vector v is destroyed, it is responsible for destroying all the Widgets it contains.
Suppose v has ten Widgets in it, and during destruction of the first one, an exception is thrown.
The other nine Widgets still have to be destroyed (otherwise any resources they hold would be leaked),
so v should invoke their destructors.
1)Destructors should never emit exceptions. If functions called in a destructor may throw,
the destructor should catch any exceptions, then swallow them or terminate the program.
2)If class clients need to be able to react to exceptions thrown during an operation,
the class should provide a regular (i.e., non-destructor) function that performs the operation.
Item 9: Never call virtual functions during construction or destruction
I'll begin with the recap: you shouldn't call virtual functions during construction or destruction, because the calls won't do what you think, and if they did, you'd still be unhappy. If you're a recovering Java or C# programmer, pay close attention to this Item, because this is a place where those languages zig, while C++ zags.
During base class construction, virtual functions never go down into derived classes. Instead, the object behaves as if it were of the base type. Informally speaking, during base class construction, virtual functions aren't.
It's actually more fundamental than that. During base class construction of a derived class object, the type of the object is that of the base class. Not only do virtual functions resolve to the base class, but the parts of the language using runtime type information (e.g., dynamic_cast (see Item 27) and typeid) treat the object as a base class type.
Item 11: Handle assignment to self in operator=
Make sure operator= is well-behaved when an object is assigned to itself. Techniques include comparing addresses of source and target objects, careful statement ordering, and copy-and-swap.
Item 13: Use objects to manage resources.
This simple example demonstrates the two critical aspects of using objects to manage resources:
1)Resources are acquired and immediately turned over to resource-managing objects. Above, the resource returned by createInvestment is used to initialize the auto_ptr that will manage it. In fact, the idea of using objects to manage resources is often called Resource Acquisition Is Initialization (RAII), because it's so common to acquire a resource and initialize a resource-managing object in the same statement. Sometimes acquired resources are assigned to resource-managing objects instead of initializing them, but either way, every resource is immediately turned over to a resource-managing object at the time the resource is acquired.
2)Resource-managing objects use their destructors to ensure that resources are released. Because destructors are called automatically when an object is destroyed (e.g., when an object goes out of scope), resources are correctly released, regardless of how control leaves a block. Things can get tricky when the act of releasing resources can lead to exceptions being thrown, but that's a matter addressed by Item 8, so we'll not worry about it here.
Item 14: Think carefully about copying behavior in resource-managing classes
Copying RAII objects??
1)Prohibit copying. In many cases, it makes no sense to allow RAII objects to be copied. This is likely to be true for a class like Lock, because it rarely makes sense to have "copies" of synchronization primitives. 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. For Lock, that could look like this:
class Lock: private Uncopyable { // prohibit copying — see
public: // Item 6
... // as before
};
2)Reference-count the underlying resource.
3)Copy the underlying resource.
4)Transfer ownership of the underlying resource.
Item 17: Store newed objects in smart pointers in standalone statements.
Store newed objects in smart pointers in standalone statements. Failure to do this can lead to subtle resource leaks when exceptions are thrown.
Item 18: Make interfaces easy to use correctly and hard to use incorrectly
struct Day { struct Month { struct Year {
explicit Day(int d) explicit Month(int m) explicit Year(int y)
:val(d) {} :val(m) {} :val(y){}
int val; int val; int val;
}; }; };
class Date {
public:
Date(const Month& m, const Day& d, const Year& y);
...
};
Date d(30, 3, 1995); // error! wrong types
Date d(Day(30), Month(3), Year(1995)); // error! wrong types
Date d(Month(3), Day(30), Year(1995)); // okay, types are correct
Item 20: Prefer pass-by-reference-to-const to pass-by-value
1)Prefer pass-by-reference-to-const over pass-by-value. It's typically more efficient and it avoids the slicing problem.
2)The rule doesn't apply to built-in types and STL iterator and function object types. For them, pass-by-value is usually appropriate.
Item 27: Minimize casting.
1)const_cast is typically used to cast away the constness of objects. It is the only C++-style cast that can do this.
2)dynamic_cast is primarily used to perform "safe downcasting," i.e., to determine whether an object is of a particular type in an inheritance hierarchy. It is the only cast that cannot be performed using the old-style syntax. It is also the only cast that may have a significant runtime cost. (I'll provide details on this a bit later.)
3)reinterpret_cast is intended for low-level casts that yield implementation-dependent (i.e., unportable) results, e.g., casting a pointer to an int. Such casts should be rare outside low-level code. I use it only once in this book, and that's only when discussing how you might write a debugging allocator for raw memory (see Item 50).
4)static_cast can be used to force implicit conversions (e.g., non-const object to const object (as in Item 3), int to double, etc.). It can also be used to perform the reverse of many such conversions (e.g., void* pointers to typed pointers, pointer-to-base to pointer-to-derived), though it cannot cast from const to non-const objects. (Only const_cast can do that.)
Avoid casts whenever practical, especially dynamic_casts in performance-sensitive code. If a design requires casting, try to develop a cast-free alternative.
When casting is necessary, try to hide it inside a function. Clients can then call the function instead of putting casts in their own code.
Prefer C++-style casts to old-style casts. They are easier to see, and they are more specific about what they do.
Item 33: Avoid hiding inherited names
Names in derived classes hide names in base classes. Under public inheritance, this is never desirable
Item 34: Differentiate between inheritance of interface and inheritance of implementation
1)Inheritance of interface is different from inheritance of implementation. Under public inheritance, derived classes always inherit base class interfaces.
2)Pure virtual functions specify inheritance of interface only.
3)Simple (impure) virtual functions specify inheritance of interface plus inheritance of a default implementation.
4)Non-virtual functions specify inheritance of interface plus inheritance of a mandatory implementation.
Item 37: Never redefine a function's inherited default parameter value
Never redefine an inherited default parameter value, because default parameter values are statically bound, while virtual functions — the only functions you should be overriding — are dynamically bound.
class xShape
{
public:
virtual void Draw(int Color = 0) const = 0;
};
class xRectangle: public xShape
{
public:
virtual void Draw(int Color = 1) const
{
cout << "color = " << Color << endl;
}
};
xShape *pShape = new xRectangle();
pShape->Draw();
delete pShape;
output:
color = 0 // not 1 defined in xRectangle
Item 38: Model "has-a" or "is-implemented-in-terms-of" through composition
In the application domain, composition means has-a. (class Person has class address)
In the implementation domain, it means is-implemented-in-terms-of. ( set contains a list as implementation)
Item 39: Use private inheritance judiciously
1)Private inheritance means is-implemented-in-terms of. It's usually inferior to composition, but it makes sense when a derived class needs access to protected base class members or needs to redefine inherited virtual functions.
2)Unlike composition, private inheritance can enable the empty base optimization. This can be important for library developers who strive to minimize object sizes.
Item 41: Understand implicit interfaces and compile-time polymorphism
Both classes and templates support interfaces and polymorphism.
For classes, interfaces are explicit and centered on function signatures. Polymorphism occurs at runtime through virtual functions.
For template parameters, interfaces are implicit and based on valid expressions. Polymorphism occurs during compilation through template instantiation and function overloading resolution.
Item 42: Understand the two meanings of typename
When declaring template parameters, class and typename are interchangeable.
Use typename to identify nested dependent type names, except in base class lists or as a base class identifier in a member initialization list.
Item 43: Know how to access names in templatized base classes
In derived class templates, refer to names in base class templates via a "this->" prefix, via using declarations, or via an explicit base class qualification.
Item 44: Factor parameter-independent code out of templates
Templates generate multiple classes and multiple functions, so any template code not dependent on a template parameter causes bloat.
Bloat due to non-type template parameters can often be eliminated by replacing template parameters with function parameters or class data members.
Bloat due to type parameters can be reduced by sharing implementations for instantiation types with identical binary representations.
Item 45: Use member function templates to accept "all compatible types."
template<typename T>
class SmartPtr {
public:
template<typename U> // member template
SmartPtr(const SmartPtr<U>& other); // for a "generalized
... // copy constructor"
};
This says that for every type T and every type U, a SmartPtr<T> can be created from a SmartPtr<U>, because SmartPtr<T> has a constructor that takes a SmartPtr<U> parameter. Constructors like this — ones that create one object from another object whose type is a different instantiation of the same template (e.g., create a SmartPtr<T> from a SmartPtr<U>) — are sometimes known as generalized copy constructors.
The generalized copy constructor above is not declared explicit. That's deliberate. Type conversions among built-in pointer types (e.g., from derived to base class pointers) are implicit and require no cast, so it's reasonable for smart pointers to emulate that behavior. Omitting explicit on the templatized constructor does just that.
As declared, the generalized copy constructor for SmartPtr offers more than we want. Yes, we want to be able to create a SmartPtr<Top> from a SmartPtr<Bottom>, but we don't want to be able to create a SmartPtr<Bottom> from a SmartPtr<Top>, as that's contrary to the meaning of public inheritance (see Item 32). We also don't want to be able to create a SmartPtr<int> from a SmartPtr<double>, because there is no corresponding implicit conversion from int* to double*. Somehow, we have to cull the herd of member functions that this member template will generate.
1)Use member function templates to generate functions that accept all compatible types.
2)If you declare member templates for generalized copy construction or generalized assignment, you'll still need to declare the normal copy constructor and copy assignment operator, too.
Item 47: Use traits classes for information about types
Traits classes make information about types available during compilation. They're implemented using templates and template specializations.
In conjunction with overloading, traits classes make it possible to perform compile-time if...else tests on types.
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // random access
std::random_access_iterator_tag) // iterators
{
iter += d;
}
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // bidirectional
std::bidirectional_iterator_tag) // iterators
{
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
template<typename IterT, typename DistT> // use this impl for
void doAdvance(IterT& iter, DistT d, // input iterators
std::input_iterator_tag)
{
if (d < 0 ) {
throw std::out_of_range("Negative distance"); // see below
}
while (d--) ++iter;
}
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
doAdvance( // call the version
iter, d, // of doAdvance
typename // that is
std::iterator_traits<IterT>::iterator_category() // appropriate for
); // iter's iterator
}
Item 48: Be aware of template metaprogramming
template<unsigned n> // general case: the value of
struct Factorial { // Factorial<n> is n times the value
// of Factorial<n-1>
enum { value = n * Factorial<n-1>::value };
};
template<> // special case: the value of
struct Factorial<0> { // Factorial<0> is 1
enum { value = 1 };
};
int main()
{
std::cout << Factorial<5>::value; // prints 120
std::cout << Factorial<10>::value; // prints 3628800
}
C++ 内存管理基础之 new & delete
在 C++ 中 new operator 与 operator new 非一回事也, new operator (即所谓的 new expression )乃语言内建,咱们是没法改变其行为的,当你写 string *ps = new string("Hands up!") 时,你所使用的 new 是所谓的 new operator ,它其实干了两件事:一、分配足够的内存(实际大小是大于所创建的对象大小) 二、调用对象构造函数, new operator 永远干这两件事。上面的那段代码大约反映以下的行为:
void *mem = operator new(sizeof(string));
call string::string("Hands up!") on *mem;
string *ps = static_cast<string*>(mem);
也就是说 operator new 仅仅分配内存(就像 malloc 一样),我们能够做的仅仅是重载 operator new ,为自己的类创建一个定制的内存管理方案
New 的 3 种形态: new operator 、 operator new 、 placement new
new 操作符 (new 表达式 , new operator , new expression): 通常我们调用 X * pX = new X 时使用的就是这个操作符 , 它由语言内建 , 不能重载 , 不能改变其行为 . 它包括分配内存的 operator new 和调用构造函数的 placement new 。 new 关键字实际上做了三件事:获得一块内存空间、调用构造函数、返回正确的指针。当然,如果我们创建的是简单类型的变量,那么第二步会被省略。
operator new : operator new 是一个函数 , void * operator new(size_t size) 。它分配指定大小的内存 , 可以被重载 , 可以添加额外的参数 , 但第一个参数必须为 size_t 。 operator new 除了被 new operator 调用外也可以直接被调用 : void * rawMem = operator new(sizeof(X)) 。这种用法和调用 malloc 一样。
placement new : placement new 在一块指定的内存上调用构造函数 , 包含头文件 <new> 之后也可以直接使用 placement new: X * pX = new (rawMem) X 。