Effective C++ v3 summaries

 

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值