gdb(1):启动和退出

The most usual way to start gdb is with one argument, specifying an executable program:
gdb program
You can also start with both an executable program and a core file specified:
gdb program core
You can, instead, specify a process ID as a second argument, if you want to debug a running process:
gdb program 1234
would attach gdb to process 1234.
You can optionally have gdb pass any arguments after the executable file to the inferior using —args. This option stops option processing.
gdb —args gcc -O2 -c foo.c

To exit gdb, use the quit command (abbreviated q), or type an end-of-file character (usually Ctrl-d). If you do not supply expression, gdb will terminate normally; otherwise it will terminate using the result of expression as the error code.

An interrupt (often Ctrl-c) does not exit from gdb, but rather terminates the action of any gdb command that is in progress and returns to gdb command level. It is safe to type the interrupt character at any time because gdb does not allow it to take effect until a time when it is safe.

If you need to execute occasional shell commands during your debugging session, there is no need to leave or suspend gdb; you can just use the shell command.
shell command-string Invoke a standard shell to execute command-string.
The utility make is often needed in development environments. You do not have to use the shell command for this purpose in gdb:
make make-args Execute the make program with the specified arguments. This is equivalent to ‘shell make make-args’.

Command Completion
you might want to set a breakpoint on a subroutine whose name begins with ‘make’, but when you type b
make_TAB gdb just sounds the bell. Typing TAB again displays all the function names in
your program that begin with those characters, for example:
(gdb) b make
TAB
gdb sounds bell; press TAB again, to see:
make_a_section_from_file make_environ
make_abs_section make_function_type

gcc, the gnu C/C++ compiler, supports ‘-g’ with or without ‘-O’, making it possible
to debug optimized code. We recommend that you always use ‘-g’ whenever you compile
a program. You may think your program is correct, but there is no sense in pushing your
luck.

Use the run command to start your program under gdb. The arguments to your program can be specified by the arguments of the run command.
The ‘start’ command does the equivalent of setting a temporary breakpoint
at the beginning of the main procedure and then invoking the ‘run’ command.

attach process-id This command attaches to a running process—one that was started outside
gdb.The first thing gdb does after arranging to debug the specified process is to stop it.

When you have finished debugging the attached process, you can use the detach
command to release it from gdb control. Detaching the process continues its
execution.

On certain operating systems, gdb is able to save a snapshot of a program’s state, called
a checkpoint, and come back to it later.

Thus, if you’re stepping thru a program and you think you’re getting close to the point
where things go wrong, you can save a checkpoint. Then, if you accidentally go too far and
miss the critical statement, instead of having to restart your program from the beginning,
you can just go back to the checkpoint and start again from there.
This can be especially useful if it takes a lot of time or steps to reach the point where
you think the bug occurs.
checkpoint
Save a snapshot of the debugged program’s current execution state. The
checkpoint command takes no arguments, but each checkpoint is assigned
a small integer id, similar to a breakpoint id.
info checkpoints
restart checkpoint-id
delete checkpoint checkpoint-id

最让人头疼的歧义most vexing parse

The most vexing parse is a specific form of syntactic ambiguity resolution in the C++ programming language.

class Timer {
public:
Timer();
};

class TimeKeeper {
public:
TimeKeeper(const Timer& t);

int get_time();
};

int main() {
TimeKeeper time_keeper(Timer());
return time_keeper.get_time();
}

The line TimeKeeper time_keeper(Timer()); is ambiguous, since it could be interpreted either as

  1. a variable definition for variable time_keeper of class TimeKeeper, initialized with an anonymous instance of class Timer or
  2. a function declaration for a function time_keeper which returns an object of type TimeKeeper and has a single (unnamed) parameter which is a function returning type Timer (and taking no input).

Most programmers expect the first, but the C++ standard requires it to be interpreted as the second. One way to force the compiler to consider this as a variable definition is to add an extra pair of parentheses:TimeKeeper time_keeper( (Timer()) );

Using the new uniform initialization syntax introduced in C++11 solves this issue (and many more headaches related to initialization styles in C++). The problematic code is then unambiguous when braces are used:

TimeKeeper time_keeper{Timer{}};

不可复制noncopyable

class noncopyable
{
protected:
noncopyable() {}
~noncopyable() {}
private:
noncopyable(const noncopyable &orig);
noncopyable & operator=(const noncopyable &rhs);
};

class A : private noncopyable
{
public:
A & operator=(const A &rhs) {}
};

class B : private noncopyable
{};

int main()
{

A a1,a2;
B b1,b2;

a1 = a2; // ok
b1 = b2; // error
}

首先,构造函数和析构函数设置为protected,这样子类能够调用,但是却不能被外部调用。其次,拷贝构造函数和赋值运算符设置为私有的,并且只声明不实现。对于类A而言,它自己又实现了赋值运算符,并且在实现中它没有去调用父类的赋值运算符,所以是ok的。对于类B而言,它自己没有实现赋值运算符,那么编译器会自动合成一个,合成的赋值运算符会去调用父类的私有的赋值运算符从而导致编译失败。如果没有a1=a2这个语句,即没有需要赋值运算符的地方,则编译器不会为类A自动合成赋值运算符。

线程同步之条件变量

The common use of condition vars is something like:

thread 1:
pthread_mutex_lock(&mutex);

while (!condition)
pthread_cond_wait(&cond, &mutex);
/* do something that requires holding the mutex and condition is true */
pthread_mutex_unlock(&mutex);

thread2:
pthread_mutex_lock(&mutex);

/* do something that might make condition true */
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

1. pthread_cond_wait

pthread_cond_wait到底做了什么事情呢?
(1)释放mutex
(2)把当前线程加入到此条件变量的等待线程队列,然后睡眠
(3)当其他线程调用pthread_cond_signal或者pthread_cond_broadcast后,调用pthread_cond_wait的线程从睡眠状态醒来,然后试图重新获取mutex,如果此时能够获取mutex则从pthread_cond_wait返回,如果不能获取mutex则进入阻塞状态,等待其他线程释放mutex

其中,系统保证第一步和第二步是原子操作。为什么是原子操作呢?因为如果不是原子操作的话,会存在竞争条件(race condition)。当前线程释放mutex后,其他线程可以获得该mutex并调用pthread_cond_signal,然后当前线程再执行第二步,则会一直处于睡眠状态(直到其他线程再次调用pthread_cond_signal),即错过了一次条件变量变为真的情况。

2. pthread_cond_signal

pthread_cond_signal does not unlock the mutex (it can’t as it has no reference to the mutex, so how could it know what to unlock?) In fact, the signal need not have any connection to the mutex; the signalling thread does not need to hold the mutex, though for most algorithms based on condition variables it will.

3. 线程1和线程2的并发执行情况

所以,线程1和线程2的并发执行情况是这样的:
(1)线程1获得mutex
(2)线程1测试条件为满足,则调用pthread_cond_wait,导致其释放mutex并把自己加入到条件变量的等待线程队列,然后睡眠
(3)线程2获得mutex
(4)线程2将条件设置为true,调用pthread|_cond_signal唤醒线程1,线程1被唤醒后,试图重新获取mutex,因为此时mutex被线程2持有,所以线程1被阻塞在获取mutex的操作上。
(5)线程2释放mutex
(6)线程1获得mutex,并从pthread_cond_wait返回。

4. pthread_cond_signal和pthread_mutex_unlock的顺序

我们看到线程2是先调用pthread_cond_signal,再释放mutex的。那么它们的执行顺序能反过来吗?答案是不能,因为反过来的话存在竞争条件。线程2在释放mutex后,线程1获取mutex,进行条件测试发现为false,于是线程1准备调用pthread_cond_wait。但就在此时,线程2执行pthread_cond_signal。之后,线程1再调用pthread_cond_wait导致其阻塞。即错过了一次条件变量变为真的情况。

5. 后记

A spin lock is like a mutex, except that instead of blocking a process by sleeping, the process is blocked by busy-waiting (spinning) until the lock can be acquired.

c++ primer笔记27: Pointer to Class Member

1. Pointer to Class Member

A pointer to member embodies the type of the class as well as the type of the member. Pointers to member apply only to non static members of a class. static class members are not part of any object, so no special syntax is needed to point to a static member. Pointers to static members are ordinary pointers.

class Screen {
public:
typedef std::string::size_type index;
char get() const;
char get(index ht, index wd) const;
private:
std::string contents;
index cursor;
index height, width;
};
## 2. Defining a Pointer to Data Member
string Screen::*ps_Screen = &Screen::contents;

普通指针使用*来标示,而指向类的成员函数的指针使用ClassName::*标示。需要注意的是,指向类的数据成员的指针并非指针,因为它既不包含地址,行为也不像指针。与常规指针不同,一个指向成员的指针并不指向一个具体的内存位置,它指向的是一个类的特定成员,而不是指向一个特定对象里的特定成员。通常最清晰的做法是将指向数据成员的指针看作 为一个偏移量 。 C++标准并没有说该如何实现指向成员的指针,大多数编译器都将指向数据成员的指针实现为一个整数,其中包含被指向成员的偏移量。另外加上1(加1是为了让0值可以表示一个空的数据成员指针)。 这个偏移量告诉你,一个特定成员的位置距离对象的起点有多少个字节。一个类成员的偏移量在任何对象中都是相同的。 为啥偏移量会加1呢?这主要用来区分“没有指向任何数据成员的指针”和“指向第一个数据成员的指针”这两种情况。考虑下面这样的例子:

float Point3d::*p1 =0;
float Point3d::*p2 = &Point3d::x;

if( p1 == p2 ){
cout <<” p1 & p2 contain the same value.”;
cout <<”they must address the same member!”<<endl;
}

3. Defining a Pointer to Member Function

A pointer to a member function must match the type of the function to which it points, in three ways:

  • The type and number of the function parameters, including whether the member is const.
  • The return type.
  • The class type of which it is a member
    char (Screen::*pmf)() const = &Screen::get;

    char (Screen::*pmf2)(Screen::index, Screen::index) const;
    pmf2 = &Screen::get;

    typedef char (Screen::*Action)(Screen::index, Screen::index) const;
    Action get = &Screen::get;

4. Using a Pointer to Member Function

char (Screen::*pmf)() const = &Screen::get;
Screen myScreen;
char c1 = myScreen.get(); // call get on myScreen
char c2 = (myScreen.*pmf)(); // equivalent call to get
Screen *pScreen = &myScreen;
c1 = pScreen->get(); // call get on object to which pScreen points
c2 = (pScreen->*pmf)(); // equivalent call to get

5. Using a Pointer to Data Member

Screen::index Screen::*pindex = &Screen::width;
Screen myScreen;
// equivalent ways to fetch width member of myScreen
Screen::index ind1 = myScreen.width; // directly
Screen::index ind2 = myScreen.*pindex; // dereference to get width
Screen *pScreen;
// equivalent ways to fetch width member of *pScreen
ind1 = pScreen->width; // directly
ind2 = pScreen->*pindex; // dereference pindex to get width

6. Pointer-to-Member Function Tables

One common use for function pointers and for pointers to member functions is to store them in a function table. A function table is a collection of function pointers from which a given call is selected at run time.

class Screen {
public:
// other interface and implementation members as before
Screen& home(); // cursor movement functions
Screen& forward();
Screen& back();
Screen& up();
Screen& down();
};

class Screen {
public:
// other interface and implementation members as before
// Action is pointer that can be assigned any of the cursor movement members
typedef Screen& (Screen::*Action)();
static Action Menu[]; // function table
public:
// specify which direction to move
enum Directions { HOME, FORWARD, BACK, UP, DOWN };
Screen& move(Directions);
};

Screen& Screen::move(Directions cm)
{
// fetch the element in Menu indexed by cm
// run that member on behalf of this object
(this->*Menu[cm])();
return *this;
}

// What's left is to define and initialize the table itself:
Screen::Action Screen::Menu[] = { &Screen::home,
&Screen::forward,
&Screen::back,
&Screen::up,
&Screen::down,
};

Screen myScreen;
myScreen.move(Screen::HOME); // invokes myScreen.home
myScreen.move(Screen::DOWN); // invokes myScreen.down

c++ primer笔记26: RTTI

1. Run-Time Type Identification

RTTI is provided through two operators:

  • The typeid operator, which returns the actual type of the object referred to by a pointer or a reference
  • The dynamic_cast operator, which safely converts from a pointer or reference to a base type to a pointer or reference to a derived type

These operators return dynamic type information only for classes with one or more virtual functions. For all other types, information for the static (i.e., compile-time) type is returned. Dynamic casts should be used with caution. Whenever possible, it is much better to define and use a virtual function rather than to take over managing the types directly.

2. The dynamic_cast Operator

The dynamic_cast operator can be used to convert a reference or pointer to an object of base type to a reference or pointer to another type in the same hierarchy.

Unlike other casts, a dynamic_cast involves a run-time type check. If the object bound to the reference or pointer is not an object of the target type, then the dynamic_cast fails. If an dynamic_cast to a pointer type fails, the result of the dynamic_cast is the value 0. If a dynamic_cast to a reference type fails, then an exception of type bad_cast is thrown. The verification that the dynamic_cast operator performs must be done at run time.

if (Derived *derivedPtr = dynamic_cast<Derived*>(basePtr))
{
// use the Derived object to which derivedPtr points
} else { // BasePtr points at a Base object
// use the Base object to which basePtr points
}

Performing a dynamic_cast in a condition ensures that the cast and test of its result are done in a single expression. Another advantage is that the pointer is not accessible outside the if. If the cast fails, then the unbound pointer is not available for use in later cases where the test might be forgotten.

void f(const Base &b)
{

try {
const Derived &d = dynamic_cast<const Derived&>(b);
// use the Derived object to which b referred
} catch (bad_cast) {
// handle the fact that the cast failed
}
}

3. The typeid Operator

typeid(e) where e is any expression or a type name. When the operand is not of class type or is a class without virtual functions, then the typeid operator indicates the static type of the operand. When the operand has a class-type that defines at least one virtual function, then the type is evaluated at run time. The result of a typeid operation is a reference to an object of a library type named type_info.

Base *bp;
Derived *dp;
// compare type at run time of two objects
if (typeid(*bp) == typeid(*dp)) {
// bp and dp point to objects of the same type
}
// test whether run time type is a specific type
if (typeid(*bp) == typeid(Derived)) {
// bp actually points to a Derived
}

Note that the operands to the typeid are expressions that are objectswe tested *bp , not bp. Dynamic type information is returned only if the operand to typeid is an object of a class type with virtual functions. Testing a pointer (as opposed to the object to which the pointer points) returns the static, compile-time type of the pointer.

If the value of a pointer p is 0, then typeid(p) throws a bad_typeid exception if the type of p is a type with virtual functions. If the type of p does not define any virtuals, then the value of p is irrelevant. As when evaluating a sizeof expression the compiler does not evaluate p . It uses the static type of p , which does not require that p itself be a valid pointer.

c++ primer笔记25: Optimizing Memory Allocation

1. A Memory-Allocator Base Class

One common strategy is to preallocate a block of raw memory to hold unconstructed objects. When new elements are created, they could be constructed in one of these preallocated objects. When elements are freed, we’d put them back in the block of preallocated objects rather than actually returning memory to the system. This kind of strategy is often known as maintaining a freelist . The freelist might be implemented as a linked list of objects that have been allocated but not constructed. We’ll define a new class that we’ll name CachedObj to handle the freelist.

The CachedObj class will have a simple interface: Its only job is to allocate and manage a freelist of allocated but unconstructed objects. This class will define a member operator new that will return the next element from the freelist, removing it from the freelist. The operator new will allocate new raw memory whenever the freelist becomes empty. The class will also define operator delete to put an element back on the freelist when an object is destroyed. Classes that wish to use a freelist allocation strategy for their own types will inherit from CachedObj.

As we’ll see, CachedObj may be used only for types that are not involved in an inheritance hierarchy. Unlike the member new and
delete operations, CachedObj has no way to allocate different sized objects depending on the actual type of the object: Its freelist holds objects of a single size. Hence, it may be used only for classes, such as QueueItem , that do not serve as base classes. The data members defined by the CachedObj class, and inherited by its derived classes, are:

  • A static pointer to the head of the freelist
  • A member named next that points from one CachedObj to the next

The next pointer chains the elements together onto the freelist. Each type that we derive from CachedObj will contain its own type-specific data plus a single pointer inherited from the CachedObj base class. When the object is in use, this pointer is meaningless and not used. When the object is available for use and is on the freelist, then the next pointer is used to point to the next available object.

The only remaining question is what types to use for the pointers in CachedObj. We’d like to use the freelist approach for any type, so the class will be a template. The pointers will point to an object of the template type:

template <class T>
class CachedObj
{
public:
CachedObj() : next(NULL) {}
void * operator new(size_t);
void operator delete(void *, size_t);
virtual ~CachedObj() {}

private:
static void add_to_freelist(T*);
static allocator<T> alloc_mem;
static T *freeStore;
static const size_t chunk;
T *next;
};

The static members manage the freelist. These members are declared as static because there is only one freelist maintained for all the objects of a given type. The freeStore pointer points to the head of the freelist. The member named chunk specifies the number of objects that will be allocated each time the freelist is empty.

template <class T>
void * CachedObj<T>::operator new(size_t sz)
{

// new should only be asked to build a T, not an object
// derived from T; check that right size is requested

if(sz != sizeof(T))
throw runtime_error("CachedObj: wrong size object in operator new");

if(NULL == freeStore)
{
T *array = alloc_mem.allocate(chunk);

for(size_t i = 0; i < sz; ++i)
{
add_to_freelist(&array[i]);
}
}

T *p = freeStore;
freeStore = freeStore->CachedObj<T>::next;
return p; // constructor of T will construct the T part of the object
}

template <class T>
void CachedObj<T>::operator delete(void *p, size_t)
{

if(NULL != p)
add_to_freelist(static_cast<T*>(p));
}

template <class T>
void CachedObj<T>::add_to_freelist(T *p)
{
p->CachedObj<T>::next = freeStore;
freeStore = p;
}

The only tricky part is the use of the next member. Recall that CachedObj is intended to be used as a base class. The objects that are allocated aren’t of type CachedObj . Instead, those objects are of a type derived from CachedObj . The type of T , therefore, will be the derived type. The pointer p is a pointer to T , not a pointer to CachedObj . If the derived class has its own member named next , then writing p->next would fetch the next member of the derived class! But we want to set the next in the base, CachedObj class.

What remains is to define the static data members:

template <class T> allocator< T > CachedObj< T >::alloc_mem;
template <class T> T *CachedObj< T >::freeStore = 0;
template <class T> const size_t CachedObj< T >::chunk = 24;

Here is how we use CachedObj:

class Screen: public CachedObj<Screen> {
// interface and implementation members of class Screen are unchanged
};
template <class Type>
class QueueItem: public CachedObj< QueueItem<Type> > {
// remainder of class declaration and all member definitions unchanged
};

2. 练习题

class iStack {
public:
iStack(int capacity): stack(capacity), top(0) { }
private:
int top;
vector<int> stack;
};

(a) iStack *ps = new iStack(20); // ok
(b) iStack *ps2 = new const iStack(15); // error
(c) iStack *ps3 = new iStack[ 100 ]; // error

注意iStack中类类型的数据成员stack的初始化方式。b错误是因为返回的是const iStack *类型的指针。c错误是因为使用new表达式动态分配数组时,如果数组元素具有类类型,则使用该类的默认构造函数初始化,但是iStack没有。

c++ primer笔记24: Optimizing Memory Allocation

1. operator new and operator delete Functions

We have learned how to use the allocate class. Now we are using the more primitive library facilities. string * sp = new string("initialized"); Three steps actually take place. First, the expression calls a library function named operator new to allocate raw, untyped memory large enough to hold an object of the specified type. Next, a constructor for the type is run to construct the object from the specified initializers. Finally, a pointer to the newly allocated and constructed object is returned. When we use a delete expression to delete a dynamically allocated object: delete sp; Two steps happen. First, the appropriate destructor is run on the object to which sp points. Then, the memory used by the object is freed by calling a library function named operator delete.

The library functions operator new and operator delete are misleadingly named. Unlike other operator functions, such as operator= , these functions do not overload the new or delete expressions. In fact, we cannot redefine the behavior of the new and delete expressions. A new expression executes by calling an operator new function to obtain memory and then constructs an object in that memory. A delete expression executes by destroying an object and then calls an operator delete function to free the memory used by the object.

There are two overloaded versions of operator new and operator delete functions. Each version supports the related new and delete expression:

void *operator new(size_t); // allocate an object
void *operator new[](size_t); // allocate an array
void *operator delete(void*); // free an object
void *operator delete[](void*); // free an array

T* newelements = alloc.allocate(newcapacity);
// which could be rewritten as
T* newelements = static_cast<T*>(operator new[](newcapacity * sizeof(T)));

alloc.deallocate(elements, end - elements);
// which could be rewritten as
operator delete[](elements);

In general, it is more type-safe to use an allocator rather than using the operator new and operator delete functions directly.

2. Placement new Expressions

The library functions operator new and operator delete are lower-level versions of the allocator members allocate and deallocate. Each allocates but does not initialize memory. There are also lower-level alternatives to the allocator members construct and destroy. These members initialize and destroy objects in space allocated by an allocator object. Placement new allows us to construct an object at a specific, preallocated memory address. The form of a placement new expression is:

new (place_address) type
new (place_address) type (initializer-list)

alloc.construct(first_free, t);
// would be replaced by the equivalent placement new expression
// copy t into element addressed by first_free
new (first_free) T(t);

Placement new expressions are more flexible than the construct member of class allocator. When placement new initializes an object, it can use any constructor, and builds the object directly. The construct function always uses the copy constructor.

allocator<string> alloc;
string *sp = alloc.allocate(2); // allocate space to hold 2 strings
// two ways to construct a string from a pair of iterators
new (sp) string(b, e); // construct directly in place
alloc.construct(sp + 1, string(b, e)); // build and copy a temporary

The placement new expression uses the string constructor that takes a pair of iterators to construct the string directly in the space to which sp points. When we call construct, we must first construct the string from the iterators to get a string object to pass to construct. That function then uses the string copy constructor to copy that unnamed, temporary string into the object to which sp points.

3. Explicit Destructor Invocation

for (T *p = first_free; p != elements; /* empty */ )
alloc.destroy(--p);
for (T *p = first_free; p != elements; /* empty */ )
p->~T(); // call the destructor

The effect of calling the destructor explicitly is that the object itself is properly cleaned up. However, the memory in which the object resided is not freed. We can reuse the space if desired.

4. Class Specific new and delete

Another way to optimize memory allocation involves optimizing the behavior of new expressions. As an example, consider the Queue class from Chapter 16. That class doesn’t hold its elements directly. Instead, it uses new expressions to allocate objects of type QueueItem.

It might be possible to improve the performance of Queue by preallocating a block of raw memory to hold QueueItem objects. When a new QueueItem object is created, it could be constructed in this preallocated space. When QueueItem objects are freed, we’d put them back in the block of preallocated objects rather than actually returning memory to the system.

By default, new expressions allocate memory by calling the version of operator new that is defined by the library. A class may manage the memory used for objects of its type by defining its own members named operator new and operator delete.

When the compiler sees a new or delete expression for a class type, it looks to see if the class has a member operator new or operator delete . If the class defines (or inherits) its own member new and delete functions, then those functions are used to allocate and free the memory for the object. Otherwise, the standard library versions of these functions are called.

When we optimize the behavior of new and delete , we need only define new versions of the operator new and operator delete . The new and delete expressions themselves take care of constructing and destroying the objects. If a class defines either of these members, it should define both of them.

A class member operator new function must have a return type of void* and take a parameter of type size_t . The function’s size_t parameter is initialized by the new expression with the size, in bytes, of the amount of memory to allocate. A class member operator delete function must have a void return type. It can be defined to take a single parameter of type void or to take two parameters, a void and a size_t . The void* parameter is initialized by the delete expression with the pointer that was delete d. That pointer might be a null pointer. If present, the size_t parameter is initialized automatically by the compiler with the size in bytes of the object addressed by the first parameter.

The size_t parameter is unnecessary unless the class is part of an inheritance hierarchy. When we delete a pointer to a type in an inheritance hierarchy, the pointer might point to a base-class object or an object of a derived class. In general, the size of a derived-type object is larger than the size of a base-class object. If the base class has a virtual destructor, then the size passed to operator delete will vary depending on the dynamic type of the object to which the deleted pointer points. If the base class does not have a virtual destructor, then, as usual, the behavior of deleting a pointer to a derived object through a base-class pointer is undefined.

These functions are implicitly static members. There is no need to declare them static explicitly, although it is legal to do so. The member new and delete functions must be static because they are used either before the object is constructed ( operator
new ) or after it has been destroyed ( operator delete ). There are, therefore, no member data for these functions to manipulate. As with any other static member function, new and delete may access only static members of their class directly.

We can also define member operator new[] and operator delete[] to manage arrays of the class type. If these operator functions exist, the compiler uses them in place of the global versions.

A class member operator new[] must have a return type of void* and take a first parameter of type size_t . The operator’s size_t parameter is initialized automatically with a value that represents the number of bytes required to store an array of the given number of elements of the specified type.

The member operator delete[] must have a void return type and a first parameter of type void . The operator’s void parameter is initialized automatically with a value that represents the beginning of the storage in which the array is stored.

The operator delete[] for a class may also have two parameters instead of one, the second parameter being a size_t . If present, the additional parameter is initialized automatically by the compiler with the size in bytes of the storage required to store the array.

A user of a class that defines its own member new and delete can force a new or delete expression to use the global library functions through the use of the global scope resolution operator. If the user writes

Type *p = ::new Type; // uses global operator new
::delete p; // uses global operator delete

If storage was allocated with a new expression invoking the global operator new function, then the delete expression should also
invoke the global operator delete function.

c++ primer笔记23: Optimizing Memory Allocation

1. Optimizing Memory Allocation

A common strategy is to preallocate memory to be used when new objects are created, constructing each new object in preallocated memory as needed. We need to decouple memory allocation from object construction. The obvious reason to decouple allocation and construction is that constructing objects in preallocated memory is wasteful.

In C++, When we use a new expression, memory is allocated, and an object is constructed in that memory. When we use a delete expression, a destructor is called to destroy the object and the memory used by the object is returned to the system. When we take over memory allocation, we must deal with both these tasks. When we allocate raw memory, we must construct object(s) in that memory. Before freeing that memory, we must ensure that the objects are properly destroyed. C++ provides two ways to allocate and free unconstructed, raw memory:

  • The allocator class, which provides type-aware memory allocation. This class supports an abstract interface to allocating memory and subsequently using that memory to hold objects.
  • The library operator new and operator delete functions, which allocate and free raw, untyped memory of a requested size.

C++ also provides various ways to construct and destroy objects in raw memory:

  • The allocator class defines members named construct and destroy. The construct member initializes objects in unconstructed memory;
    the destroy member runs the appropriate destructor on objects.
  • The placement new expression takes a pointer to unconstructed memory and initializes an object or an array in that space.
  • We can directly call an object’s destructor to destroy the object. Running the destructor does not free the memory in which the object resides.
  • The algorithms uninitialized_fill and uninitialized_copy execute like the fill and copy algorithms except that they construct objects in their destination rather than assigning to them.
// The behavior of copy template is equivalent to:
template<class InputIterator, class OutputIterator>
OutputIterator copy (InputIterator first, InputIterator last, OutputIterator result)
{
while (first!=last) {
*result = *first;
++result; ++first;
}
return result;
}

也就是说copy是赋值行为,uninitialized_copy是在一块raw内存中的初始化行为。赋值行为意味着将释放原来的数据。Assigning to an object in unconstructed memory rather than initializing it is undefined. For many classes, doing so causes a crash at run time. Assignment involves freeing the existing object. If there is no existing object, then the actions in theassignment operator can have disastrous effects.

Modern C++ programs ordinarily ought to use the allocator class to allocate memory. It is safer and more flexible. However, when
constructing objects, the placement new expression is more flexible than the allocator::construct member. There are some cases where placement new must be used. 因为allocator::construct只能使用拷贝构造函数来构造对象。

2. The allocator Class

Class allocator  
allocator a; Defines an allocator object named a that can allocate memory or construct objects of type T.
a.allocate(n) Allocates raw, unconstructed memory to hold n objects of type T.
a.deallocate(p, n) Deallocates memory that held n objects of type T starting at address contained in the T* pointer named p. It is the user’s responsibility to run destroy on any objects that were constructed in this memory before calling deallocate.
a.construct(p, t) Constructs a new element in the memory pointed to by the T* pointer p. The copy constructor of type T is run to initialize the object from t.
a.destroy(p) Runs the destructor on the object pointed to by the T* pointer p.
uninitialized_copy(b, e, b2) Copies elements from the input range denoted by iterators b and e into unconstructed, raw memory beginning at iterator b2. The function constructs elements in the destination, rather than assigning them. The destination denoted by b2 is assumed large enough to hold a copy of the elements in the input range.
uninitialized_fill(b, e, t) Initializes objects in the range denoted by iterators b and e as a copy of t. The range is assumed to be unconstructed, raw memory. The objects are constructed using the copy constructor.
uninitialized_fill_n(b, e, t, n) Initializes at most an integral number n objects in the range denoted by iterators b and e as a copy of t. The range is assumed to be at least n elements in size. The objects are constructed using the copy constructor.

The allocator class separates allocation and object construction. When an allocator object allocates memory, it allocates space that is appropriately sized and aligned to hold objects of the given type. However, the memory it allocates is unconstructed. Users of allocator must separately construct and destroy objects placed in the memory it allocates.

3. Using allocator to Manage Class Member Data

To understand how we might use a preallocation strategy and the allocator class to manage the internal data needs of a class, let’s think a bit more about how memory allocation in the vector class might work.

// pseudo-implementation of memory allocation strategy for a vector-like class
template <class T> class Vector {
public:
Vector(): elements(0), first_free(0), end(0) { }
void push_back(const T&);
// ...
private:
static std::allocator<T> alloc; // object to get raw memory
void reallocate(); // get more space and copy existing elements
T* elements; // pointer to first element in the array
T* first_free; // pointer to first free element in the array
T* end; // pointer to one past the end of the array
// ...
};

template <class T>
void Vector<T>::push_back(const T&t)
{
if(first_free == end)
reallocate();
alloc.construct(first_free, t);
++first_free;
}

template <class T>
void Vector<T>::reallocate()
{
ptrdiff_t size = first_free - elements;
ptrdiff_t newcapacity = max(size, 1) * 2;
T *new_elements = alloc.allocate(newcapacity);
uninitialized_copy(elements, end, new_elements);

for(T *p = elements; p != end; ++p)
alloc.destroy(p);

// deallocate cannot be called on a 0 pointer
if (elements)
alloc.deallocate(elements, size);

elements = new_elements;
first_free = elements + size;
end = elements + newcapacity;
}

注意静态成员alloc在实现文件中的定义
template<class T>
allocator<T> Vector<T>::alloc;

Each Vector type defines a static data member of type allocator to allocate and construct the elements in Vectors of the given type. deallocate expects a pointer that points to space that was allocated by allocate. It is not legal to pass deallocate a zero pointer.

c++ primer笔记22: multiple inheritance

1. Multiple Inheritance

class Bear : public ZooAnimal { };
class Panda : public Bear, public Endangered { };

The constructor initializer controls only the values that are used to initialize the base classes, not the order in which the base classes are constructed. The base-class constructors are invoked in the order in which they appear in the class derivation list. For Panda , the order of base-class initialization is:

  • ZooAnimal, the ultimate base class up the hierarchy from Panda ‘s immediate base clas Bear.
  • Bear, the first immediate base class.
  • Endangered, the second immediate base, which itself has no base class.
  • Panda, the members of Panda itself are initialized, and then the body of its constructor is run.

Destructors are always invoked in the reverse order from which the constructors are run. In our example, the order in which the destructors are called is ~Panda, ~Endangered, ~Bear, ~ZooAnimal.

class X { ... };
class A { ... };
class B : public A { ... };
class C : private B { ... };
class D : public X, public C { ... };

which, if any, of the following conversions are not permitted?
D *pd = new D;
(a) X *px = pd; (c) B *pb = pd;
(b) A *pa = pd; (d) C *pc = pd;

b和c是错误的,因为C对B是私有继承的,对于C的子类而言,它完全不知道有B的存在,所有无法转化为B和A。

As is the case for single inheritance, if a class with multiple bases defines its own destructor, that destructor is responsible only for cleaning up the derived class. If the derived class defines its own copy constructor or assignment operator, then the class is responsible for copying (assigning) all the base class subparts. The base parts are automatically copied or assigned only if the derived class uses the synthesized versions of these members.

As usual, name lookup for a name used in a member function starts in the function itself. If the name is not found locally, then lookup continues in the member’s class and then searches each base class in turn. Under multiple inheritance, the search simultaneously examines all the base-class inheritance subtreesin our example, both the Endangered and the Bear / ZooAnimal subtrees are examined in parallel. If the name is found in more than one subtree, then the use of that name must explicitly specify which base class to use. Otherwise, the use of the name is ambiguous.

2. Multiple Base Classes Can Lead to Ambiguities

Assume both Bear and Endangered define a member named print . If Panda does not define that member, then a statement such as the following ying_yang.print(cout);results in a compile-time error. Although the ambiguity of the two inherited print members is reasonably obvious, it might be more surprising to learn that an error would be generated even if the two inherited functions had
different parameter lists. Similarly, it would be an error even if the print function were private in one class and public or protected in the other. Finally, if print were defined in ZooAnimal and not Bear, the call would still be in error. It’s because name lookup happens first. As always, name lookup happens in two steps: First the compiler finds a matching declaration (or, in this case, two matching declarations, which causes the ambiguity). Only then does the compiler decide whether the declaration it found is legal.

We could resolve the print ambiguity by specifying which class to use:ying_yang.Endangered::print(cout);. The best way to avoid potential ambiguities is to define a version of the function in the derived class that resolves the ambiguity.

3. Virtual Inheritance

class istream : public virtual ios { ... };
class ostream : virtual public ios { ... };
class iostream: public istream, public ostream { ... };

Members in the shared virtual base can be accessed unambiguously and directly. Similarly, if a member from the virtual base is redefined along only one derivation path, then that redefined member can be accessed directly. Under a nonvirtual derivation, both kinds of access would be ambiguous. Assume a member named X is inherited through more than one derivation path. There are three
possibilities:

  • If in each path X represents the same virtual base class member, then there is no ambiguity because a single instance of the member is shared.
  • If in one path X is a member of the virtual base class member and in another path X is a member of a subsequently derived class, there is also no ambiguitythe specialized derived class instance is given precedence over the shared virtual base class instance.
  • If along each inheritance path X represents a different member of a subsequently derived class, then the direct access of the member is ambiguous.

4. Special Initialization Semantics

To solve the duplicate-initialization problem, classes that inherit from a class that has a virtual base have special handling for initialization. In a virtual derivation, the virtual base is initialized by the most derived constructor. Although the virtual base is initialized by the most derived class, any classes that inherit immediately or indirectly from the virtual base usually also have to provide their own initializers for that base. As long as we can create independent objects of a type derived from a virtual base, that class must initialize its virtual base. These initializers are used only when we create objects of the intermediate type.

Panda::Panda(std::string name, bool onExhibit) : ZooAnimal(name, onExhibit, "Panda"),
Bear(name, onExhibit),
Raccoon(name, onExhibit),
Endangered(Endangered::critical),
sleeping_flag(false) { }

When a Panda object is created:

  • The ZooAnimal part is constructed first, using the initializers specified in the Panda constructor initializer list.
  • Next, the Bear part is constructed. The initializers for ZooAnimal Bear’s constructor initializer list are ignored.
  • Then the Raccoon part is constructed, again ignoring the ZooAnimal initializers.
  • Finally, the Panda part is constructed.

If the Panda constructor does not explicitly initialize the ZooAnimal base class, then the ZooAnimal default constructor is used. If ZooAnimal doesn’t have a default constructor, then the code is in error.

5. Constructor and Destructor Order

Virtual base classes are always constructed prior to nonvirtual base classes regardless of where they appear in the inheritance hierarchy.

The immediate base classes are examined in declaration order to determine whether there are any virtual base classes. In our example, the inheritance subtree of BookCharacter is examined first, then that of Bear , and finally that of ToyAnimal. Each subtree is examined starting at the root class down to the most derived class.

The order in which the virtual base classes are constructed for TeddyBear is ZooAnimal followed by ToyAnimal. Once the virtual base classes are constructed, the nonvirtual base-class constructors are invoked in declaration order: BookCharacter, which causes the Character constructor to be invoked, and then Bear. Thus, to create a TeddyBear, the constructors are invoked in the following order:

ZooAnimal(); // Bear's virtual base class
ToyAnimal(); // immediate virtual base class
Character(); // BookCharacter's nonvirtual base class

BookCharacter(); // immediate nonvirtual base class
Bear(); // immediate nonvirtual base class
TeddyBear(); // most derived class

where the initializers used for ZooAnimal and ToyAnimal are specified by the most derived class TeddyBear. The same construction order is used in the synthesized copy constructor; the base classes also are assigned in this order in the synthesized assignment operator. The order of base-class destructor calls is guaranteed to be the reverse order of constructor invocation.

6. example1

class Class { ... };
class Base : public Class { ... };
class Derived1 : virtual public Base { ... };
class Derived2 : virtual public Base { ... };
class MI : public Derived1, public Derived2 { ... };
class Final : public MI, public Class { ... };

What is the order of constructor and destructor for the definition of a Final object?

Class()
Base()
Derived1()
Derived2()
MI()
Class()
Final()

将首先调用虚基类Base的构造函数(导致调用Class())。

7. example2

class Base {
public:
Base(std::string);
protected:
std::string name;
};

// 假设Base有上面的构造函数,则

class Derived1 : virtual public Base
{
public:
Derived1(std::string s) : Base(s) {}
};

class Derived2 : virtual public Base
{
public:
Derived2(std::string s) : Base(s) {}
};

class MI : public Derived1, public Derived2
{
public:
MI(std::string s) : Base(s), Derived1(s), Derived2(s) {}
};

class Final : public MI, public Class
{
public:
Final(std::string s) : Base(s), MI(s) {}
};

注意,任何直接或间接继承虚基类Base的类都要必须为Base类提供显示初始化式(例如MI、Final),否则将无法创建相应类的对象。而且编译的时候还会有警告warning: direct base ‘Class’ inaccessible in ‘Final’ due to ambiguity

c++ primer笔记21: namespace

1.Classes, Namespaces, and Scope

When a name is used in a class scope, we look first in the member itself, then in the class, including any base classes. Only after exhausting the class(es) do we examine the enclosing scopes.

namespace A {
int i;
int k;
class C1 {
public:
C1(): i(0), j(0) { } // ok: initializes C1::i and C1::j
int f1()
{

return k; // returns A::k
}
int f2()
{

return h; // error: h is not defined
}
int f3();
private:
int i; // hides A::i within C1
int j;
};
int h = i; // initialized from A::i
}
// member f3 is defined outside class C1 and outside namespace A
int A::C1::f3()
{
return h; // ok: returns A::h
}

2. Argument-Dependent Lookup and Class Type Parameters

std::string s;
// ok: calls std::getline(std::istream&, const std::string&)
getline(std::cin, s);

It looks for a matching function in the current scope, the scopes enclosing the call to getline, and in the namespace(s) in which the type of cin and the string type are defined. Hence, it looks in the namespace std and finds the getline function defined by the string type.

3. Implicit Friend Declarations and Namespaces

Recall that when a class declares a friend function, a declaration for the function need not be visible. If there isn’t a declaration already visible, then the friend declaration has the effect of putting a declaration for that function or class into the surrounding scope. If a class is defined inside a namespace, then an otherwise undeclared friend function is declared in the same namespace:

namespace A {
class C {
friend void f(const C&); // makes f a member of namespace A
};
}

void f2()
{

A::C cobj;
f(cobj); // calls A::f
}

4. Candidate Functions and Namespaces

Namespaces can have two impacts on function matching. One of these should be obvious: A using declaration or directive can add functions to the candidate set. The other is much more subtle. As we saw in the previous section, name lookup for functions that have one or more class-type parameters includes the namespace in which each parameter’s class is defined. This rule also impacts how we determine the candidate set. Each namespace that defines a class used as a parameter (and those that define its base class(es)) is searched for candidate functions. Any functions in those namespaces that have the same name as the called function are added to the candidate set. These functions are added even though they otherwise are not visible at the point of the call. Functions with the matching name in those namespaces are added to the candidate set:

namespace NS {
class Item_base { /* ... */ };
void display(const Item_base&) { }
}
// Bulk_item's base class is declared in namespace NS
class Bulk_item : public NS::Item_base { };
int main() {
Bulk_item book1;
display(book1);
return 0;
}

5. Overloading and using Declarations

There is no way to write a using declaration to refer to a specific function declaration:

using NS::print(int); // error: cannot specify parameter list
using NS::print; // ok: using declarations specify names only

If a function is overloaded within a namespace, then a using declaration for the name of that function declares all the functions with that name. If there are print functions for int and double in the namespace NS, then a using declaration for NS::print makes both functions visible in the current scope.

If the using declaration introduces a function in a scope that already has a function of the same name with the same parameter list, then the using declaration is in error. Otherwise, the using declaration defines additional overloaded instances of the given name. The effect is to increase the set of candidate functions.

6. Namespaces and Templates

Declaring a template within a namespace impacts how template specializations are declared: An explicit specialization of a template must be declared in the namespace in which the generic template is defined. Otherwise, the specialization would have a different name than the template it specialized.

There are two ways to define a specialization: One is to reopen the namespace and add the definition of the specialization, which we can do because namespace definitions are discontiguous. Alternatively, we could define the specialization in the same way that we can define any namespace member outside its namespace definition: by defining the specialization using the template name qualified by the name of the namespace.

To provide our own specializations of templates defined in a namespace, we must ensure that the specialization definition is defined as being in the namespace containing the original template definition.

c++ primer笔记20: namespace

1. Namespaces

Unlike other scopes, a namespace can be defined in several parts. A namespace is made up of the sum of its separately defined parts; a namespace is cumulative. The separate parts of a namespace can be spread over multiple files. The fact that namespace definitions can be discontiguous means that we can compose a namespace from separate interface and implementation files. Thus, a namespace can be organized in the same way that we manage our own class and function definitions:

  • Namespace members that define classes and declarations for the functions and objects that are part of the class interface can be put into header files. These headers can be included by files that use namespace members.
  • The definitions of namepsace members can be put in separate source files.

Namespaces that define multiple, unrelated types should use separate files to represent each type that the namespace defines.

// ---- Sales_item.h ----
namespace cplusplus_primer {
class Sales_item { /* ... */};
Sales_item operator+(const Sales_item&, const Sales_item&);
}

// ---- Sales_item.cc ----
#include "Sales_item.h"
namespace cplusplus_primer {
// definitions for Sales_item members and overloaded operators
}

It is also possible to define a namespace member outside its namespace definition. This definition should look similar to class member functions defined outside a class. The return type and function name are qualified by the namespace name. Once the fully qualified function name is seen, we are in the scope of the namespace. Thus, references to namespace members in the parameter list and the function body can use unqualified names to reference Sales_item.

cplusplus_primer::Sales_item
cplusplus_primer::operator+(const Sales_item& lhs, const Sales_item& rhs)
{
Sales_item ret(lhs);
// ...
}

Although a namespace member can be defined outside its namespace definition, there are restrictions on where this definition can appear. Only namespaces enclosing the member declaration can contain its definition. For example, operator+ could be defined in either the cplusplus_primer namespace or at global scope. It may not be defined in an unrelated namespace.

2. Unnamed Namespaces

Unnamed namespaces are not like other namespaces; the definition of an unnamed namespace is local to a particular file and never
spans multiple text files. An unnamed namespace may be discontiguous within a given file but does not span files. Each file has its own unnamed namespace. Names defined in an unnamed namespace are used directly; after all, there is no namespace name with which to qualify them. It is not possible to use the scope operator to refer to members of unnamed namespaces.

Names defined in an unnamed namespace are found in the same scope as the scope at which the namespace is defined. If an unnamed namespace is defined at the outermost scope in the file, then names in the unnamed namespace must differ from names defined at global scope:

int i; // global declaration for i
namespace {
int i;
}
// error: ambiguous defined globally and in an unnested, unnamed namespace
i = 10;

The use of file static declarations is deprecated by the C++ standard. File statics should be avoided and unnamed namespaces used instead.

3. Using Namespace Members

Referring to namespace members as namespace_name::member_name is admittedly cumbersome, especially if the namespace name is long. Fortunately, there are ways to make it easier to use namespace members: using declarations、namespace aliases and using directives.

Header files should not contain using directives or using declarations except inside functions or other scopes. A header that includes a using directive or declaration at its top level scope has the effect of injecting that name into the file that includes the header. Headers should define only the names that are part of its interface, not names used in its own implementation.

A namespace alias can be used to associate a shorter synonym with a namespace name. For example

cplusplus_primer::QueryLib::Query tq;
// we could define and use an alias for cplusplus_primer::QueryLib
namespace Qlib = cplusplus_primer::QueryLib;
Qlib::Query tq;

It can be tempting to write programs with using directives, but doing so reintroduces all the problems inherent in name collisions
when using multiple libraries.

The scope of names introduced by a using directive is more complicated than those for using declarations. A using declaration puts the name directly in the same scope in which the using declaration itself appears. It is as if the using declaration is a local alias for the namespace member.

A using directive does not declare local aliases for the namespace member names. Rather, it has the effect of lifting the namespace members into the nearest scope that contains both the namespace itself and the using directive. One place where using directives are useful is in the implementation files for the namespace itself.

namespace blip {
int bi = 16, bj = 15, bk = 23;
}
int bj = 0; // ok: bj inside blip is hidden inside a namespace
void manip()
{
// using directive - names in blip "added" to global scope
using namespace blip;
// clash between ::bj and blip::bj
// detected only if bj is used

++bi; // sets blip::bi to 17
++bj; // error: ambiguous
// global bj or blip::bj?
++::bj; // ok: sets global bj to 1

++blip::bj; // ok: sets blip::bj to 16
int bk = 97; // local bk hides blip::bk
++bk; // sets local bk to 98
}

It is possible for names in the namespace to conflict with other names defined in the enclosing scope. For example, the blip member bj appears to manip as if it were declared at global scope. However, there is another object named bj in global scope. Such conflicts are permitted; but to use the name, we must explicitly indicate which version is wanted. Therefore, the use of bj within manip is ambiguous: The name refers both to the global variable and to the member of namespace blip.

4. Example

namespace Exercise {
int ivar = 0;
double dvar = 0;
const int limit = 1000;
}
int ivar = 0;
// position 1
void manip() {
// position 2
double dvar = 3.1416;
int iobj = limit + 1;
++ivar;
++::ivar;
}

What are the effects of the declarations and expressions in this code sample if using declarations for all the members of namespace Exercise are located at the location labeled position 1? At position 2 instead? Now answer the same question but replace the using declarations with a using directive for namespace Exercise.
解答:如果Exercise的所有成员使用using声明放在position 1,则 using Exercise::ivar导致重复定义的错误。manip中的dvar会屏蔽Exercise::dvar。如果Exercise的所有成员使用using声明放在position 2,manip中的dvar会出现重复定义的错误。++ivar访问到的是Exercise::ivar,++::ivar访问的是全部变量ivar。如果Exercise的所有成员使用using指示放在position 1,则 ++ivar;会导致二义性错误。如果Exercise的所有成员使用using指示放在position 2, ++ivar;依然会导致二义性错误。

5. Caution: Avoid Using Directives

using directives, which inject all the names from a namespace, are deceptively simple to use: With only a single statement, all the member names of a namespace are suddenly visible. Although this approach may seem simple, it can introduce its own problems. If an application uses many libraries, and if the names within these libraries are made visible with using directives, then we are back to square one, and the global namespace pollution problem reappears.

Moreover, it is possible that a working program will fail to compile when a new version of the library is introduced. This problem can arise if a new version introduces a name that conflicts with a name that the application is using.

Another problem is that ambiguity errors caused by using directives are detected only at the point of use. This late detection means that conflicts can arise long after introducing a particular library. If the program begins using a new part of the library, previously undetected collisions may arise.

Rather than relying on a using directive, it is better to use a using declaration for each namespace name used in the program. Doing so reduces the number of names injected into the namespace. Ambiguity errors caused by using declarations are detected at the point of declaration, not use, and so are easier to find and fix.

c++ primer笔记19: exception

1. Exception Specifications

An exception specification specifies that if the function throws an exception, the exception it throws will be one of the exceptions included in the specification, or it will be a type derived from one of the listed exceptions. An exception specification follows the function parameter list. An exception specification is the keyword throw followed by a (possibly empty) list of exception types enclosed in parentheses: void recoup(int) throw(runtime_error); An empty specification list says that the function does not throw any exception:void no_problem() throw(); An exception specification is part of the function’s interface. The function definition and any declarations of the function must have the same specification. If a function declaration does not specify an exception specification, the function can throw exceptions of any type.

Unfortunately, it is not possible to know at compile time whether or which exceptions a program will throw. Violations of a function’s exception specification can be detected only at run time. If a function throws an exception not listed in its specification, the library function unexpected is invoked. By default, unexpected calls terminate , which ordinarily aborts the program.

Specifying that a function will not throw any exceptions can be helpful both to users of the function and to the compiler: Knowing that a function will not throw simplifies the task of writing exception-safe code that calls that function. We can know that we need not worry about exceptions when calling it. Moreover, if the compiler knows that no exceptions will be thrown, it can perform optimizations that are suppressed for code that might throw.

2. Exception Specifications and Member Functions

class bad_alloc : public exception {
public:
bad_alloc() throw();
bad_alloc(const bad_alloc &) throw();
bad_alloc & operator=(const bad_alloc &) throw();
virtual ~bad_alloc() throw();
virtual const char* what() const throw();
};

Notice that the exception specification follows the const qualifier in const member function declarations.

3. Exception Specifications and Destructors

The above isbn_mismatch class defines its destructor as

class isbn_mismatch: public std::logic_error {
public:
virtual ~isbn_mismatch() throw() { }
};

The isbn_mismatch class inherits from logic_error , which is one of the standard exception classes. The destructors for the standard exception classes include an empty throw() specifier; they promise that they will not throw any exceptions. When we inherit from one of these classes, then our destructor must also promise not to throw any exceptions.

Our out_of_stock class had no members, and so its synthesized destructor does nothing that might throw an exception. Hence, the compiler can know that the synthesized destructor will abide by the promise not to throw.

The isbn_mismatch class has two members of class string, which means that the synthesized destructor for isbn_mismatch calls the string destructor. The C++ standard stipulates that string destructor, like any other library class destructor, will not throw an exception. However, the library destructors do not define exception specifications. In this case, we know, but the compiler doesn’t, that the string destructor won’t throw. We must define our own destructor to reinstate the promise that the destructor will not throw.

4. Exception Specifications and Virtual Functions

A virtual function in a base class may have an exception specification that differs from the exception specification of the corresponding virtual in a derived class. However, the exception specification of a derived-class virtual function must be either equally or more restrictive than the exception specification of the corresponding base-class virtual function.

class Base {
public:
virtual double f1(double) throw ();
virtual int f2(int) throw (std::logic_error);
virtual std::string f3() throw (std::logic_error, std::runtime_error);
};
class Derived : public Base {
public:
// error: exception specification is less restrictive than Base::f1's
double f1(double) throw (std::underflow_error);
// ok: same exception specification as Base::f2
int f2(int) throw (std::logic_error);
// ok: Derived f3 is more restrictive
std::string f3() throw ();
}

By restricting which exceptions the derived classes will throw to those listed by the base class, we can write our code knowing what exceptions we must handle.

5. Function Pointer Exception Specifications

An exception specification is part of a function type. As such, exception specifications can be provided in the definition of a pointer to function:void (*pf)(int) throw(runtime_error); When a pointer to function with an exception specification is initialized from (or assigned to) another pointer (or to the address of a function), the exception specifications of both pointers do not have to be identical. However, the specification of the source pointer must be at least as restrictive as the specification of the destination pointer.

void recoup(int) throw(runtime_error);
// ok: recoup is as restrictive as pf1
void (*pf1)(int) throw(runtime_error) = recoup;
// ok: recoup is more restrictive than pf2
void (*pf2)(int) throw(runtime_error, logic_error) = recoup;
// error: recoup is less restrictive than pf3
void (*pf3)(int) throw() = recoup;
// ok: recoup is more restrictive than pf4
void (*pf4)(int) = recoup;

c++ primer笔记18: exception

1. Standard exception Class Hierarchy

2. User-Defined Exception Types

class out_of_stock: public std::runtime_error {
public:
explicit out_of_stock(const std::string &s):std::runtime_error(s)
{ }

};
class isbn_mismatch: public std::logic_error {
public:
explicit isbn_mismatch(const std::string &s): std::logic_error(s)
{ }

isbn_mismatch(const std::string &s, const std::string &lhs, const std::string &rhs):
std::logic_error(s), left(lhs), right(rhs) { }
const std::string left, right;
virtual ~isbn_mismatch() throw() { }
};

3. Automatic Resource Deallocation

We know that local objects are automatically destroyed when an exception occurs. The fact that destructors are run has important implication for the design of applications.

void f()
{

vector<string> v; // local vector
string s;
while (cin >> s)
v.push_back(s); // populate the vector
string *p = new string[v.size()]; // dynamic array
// remaining processing
// it is possible that an exception occurs in this code
// function cleanup is bypassed if an exception occurs
delete [] p;
} // v destroyed automatically when the function exits

If an exception occurs inside the function, then the vector will be destroyed but the array will not be freed. The problem is that the array is not freed automatically. No matter when an exception occurs, we are guaranteed that the vector destructor is run.

4. Using Classes to Manage Resource Allocation

The fact that destructors are run leads to an important programming technique that makes programs more exception safe . By exception safe, we mean that the programs operate correctly even if an exception occurs. In this case, the “safety” comes from ensuring that any resouce that is allocated is properly freed if an exception occurs. We can guarantee that resources are properly freed by defining a class to encapsulate the acquisition and release of a resource.

5. The auto_ptr Class

The standard-library auto_ptr class is an example of the exception-safe “resource allocation is initialization” technique. The aut_ptr class is a template that takes a single type parameter. It provides exception safety for dynamically allocated
objects. The auto_ptr class is defined in the memory header. auto_ptr can be used only to manage single objects returned from
new. It does not manage dynamically allocated arrays. As we’ll see, auto_ptr has unusual behavior when copied or assigned. As a result, auto_ptrs may not be stored in the library container types.

void f()
{

int *ip = new int(42); // dynamically allocate a new object
// code that throws an exception that is not caught inside f
delete ip; // return the memory before exiting
}
void f()
{

auto_ptr<int> ap(new int(42)); // allocate a new object
// code that throws an exception that is not caught inside f
}
// auto_ptr freed automatically when function ends

Class auto_ptr  
auto_ptr<T> ap Create an unbound auto_ptr named ap.
auto_ptr<T> ap(p) Create an auto_ptr named ap that owns the object pointed to by the pointer p. This constructor is explicit.
auto_ptr<T> ap1(ap2) Create an auto_ptr named ap1 that holds the pointer originally stored in ap2. Transfers ownership to ap1; ap2 becomes an unbound auto_ptr.
ap1 = ap2 Transfers ownership from ap2 to ap1. Deletes the object to which ap1 points and makes ap1 point to the object to which ap2 points, making ap2 unbound.
~ap Destructor. Deletes the object to which ap points.
*ap Returns a reference to the object to which ap is bound.
ap-> Returns the pointer that ap holds.
ap.reset(p) If the pointer p is not the same value as ap holds, then it deletes the object to which ap points and binds ap to p.
ap.release() Returns the pointer that ap had held and makes ap unbound.
ap.get() Returns the pointer that ap holds.

To determine whether the auto_ptr object refers to an object, we can compare the return from get with 0. get should be used only to interrogate an auto_ptr or to use the returned pointer value. get should not be used as an argument to create another auto_ptr. Using get member to initialize another auto_ptr violates the class design principle that only one auto_ptr holds a given pointer at any one time. If two auto_ptrs hold the same pointer, then the pointer will be deleted twice.

6. Copy and Assignment on auto_ptr Are Destructive Operations

When we copy an auto_ptr or assign its value to another auto_ptr, ownership of the underlying object is transferred from the original to the copy. The original auto_ptr is reset to an unbound state. Unlike other copy or assignment operations, auto_ptr copy and assignment change the right-hand operand. As a result, both the left- and right-hand operands to assignment must be
modifiable lvalues. Because copy and assignment are destructive operations, auto_ptrs cannot be stored in the standard containers. The library container classes require that two objects be equal after a copy or assignment. This requirement is not met by auto_ptr. If we assign ap2 to ap1, then after the assignment ap1 != ap2.

7. Caution: Auto_ptr Pitfalls

The auto_ptr class template provides a measure of safety and convenience for handling dynamically allocated memory. To use auto_ptr correctly, we must adhere to the restrictions that the class imposes:

  • Do not use an auto_ptr to hold a pointer to a statically allocated object. Otherwise, when the auto_ptr itself is destroyed, it will attempt to delete a pointer to a nondynamically allocated object, resulting in undefined behavior.
  • Never use two auto_ptrs to refer to the same object. One obvious way to make this mistake is to use the same pointer to initialize or to reset two different auto_ptr objects. A more subtle way to make this mistake would be to use the result from get on one auto_ptr to initialize or reset another.
  • Do not use an auto_ptr to hold a pointer to a dynamically allocated array. When the auto_ptr is destroyed, it frees only a single objectit uses the plain delete operator, not the array delete [] operator.
  • Do not store an auto_ptr in a container. Containers require that the types they hold define copy and assignment to behave similarly to how those operations behave on the built-in types: After the copy (or assignment), the two objects must have the same value. auto_ptr does not meet this requirement.

c++ primer笔记17: exception

1. Exception Handling

Exception handling relies on the problem-detecting part throwing an object to a handler. The type and contents of that object allow the two parts to communicate about what went wrong.

Sales_item operator+(const Sales_item& lhs, const Sales_item& rhs)
{
if (!lhs.same_isbn(rhs))
throw runtime_error("Data must refer to same ISBN");

Sales_item ret(lhs);
return
ret += rhs;
return ret;
}

Sales_item item1, item2, sum;
while (cin >> item1 >> item2) {
try {
sum = item1 + item2;
} catch (const runtime_error &e) {
cerr << e.what() << " Try again.\n"
<< endl;
}
}

2. Throwing an Exception of Class Type

An exception is raised by throwing an object. The type of that object determines which handler will be invoked. The selected handler is the one nearest in the call chain that matches the type of the object.

Exceptions are thrown and caught in ways that are similar to how arguments are passed to functions. An exception can be an object of any type that can be passed to a nonreference parameter, meaning that it must be possible to copy objects of that type. Recall that when we pass an argument of array or function type, that argument is automatically converted to an pointer. The same automatic conversion happens for objects that are thrown. As a consequence, there are no exceptions of array or function types. Instead, if we throw an array, the thrown object is converted to a pointer to the first element in the array. Similarly, if we throw a function, the function is converted to a pointer to the function. The fact that control passes from one location to another has two important implications:

  • Functions along the call chain are prematurely exited. Following section discusses what happens when functions are exited due to an exception.
  • In general, the storage that is local to a block that throws an exception is not around when the exception is handled.

Because local storage is freed while handling an exception, the object that is thrown is not stored locally. Instead, the throw expression is used to initialize a special object referred to as the exception object . The exception object is managed by the compiler and is guaranteed to reside in space that will be accessible to whatever catch is invoked. This object is created by a
throw , and is initialized as a copy of the expression that is thrown. The exception object is passed to the corresponding catch and is destroyed after the exception is completely handled. The exception object is created by copying the result of the thrown
expression; that result must be of a type that can be copied. What’s important to know is how the form of the throw expression interacts with types related by inheritance. When an exception is thrown, the static, compile-time type of the thrown object determines the type of the exception object.

The one case where it matters that a throw expression throws the static type is if we dereference a pointer in a throw. The result of dereferencing a pointer is an object whose type matches the type of the pointer. If the pointer points to a type from an inheritance hierarchy, it is possible that the type of the object to which the pointer points is different from the type of
the pointer. Regardless of the object’s actual type, the type of the exception object matches the static type of the pointer. If that pointer is a base-class type pointer that points to a derived-type object, then that object is sliced down; only the base-class part is thrown.

In particular, it is always an error to throw a pointer to a local object for the same reasons as it is an error to return a pointer to a local object from a function. It is usually a bad idea to tHRow a pointer: Throwing a pointer requires that the object to which the pointer points exist wherever the corresponding handler resides.

3. Stack Unwinding

When an exception is thrown, execution of the current function is suspended and the search begins for a matching catch clause. The search starts by checking whether the tHRow itself is located inside a try block. If so, the catch clauses associated with that try are examined to see if one of them matches the thrown object. If a matching catch is found, the exception is
handled. If no catch is found, the current function is exitedits memory is freed and local objects are destroyed and the search continues in the calling function.

If the call to the function that threw is in a try block, then the catch clauses associated with that try are examined. If a matching catch is found, the exception is handled. If no matching catch is found, the calling function is also exited, and the search continues in the function that called this one.

This process, known as stack unwinding, continues up the chain of nested function calls until a catch clause for the exception is found. As soon as a catch clause that can handle the exception is found, that catch is entered, and execution continues within this handler. When the catch completes, execution continues at the point immediately after the last catch clause associated with that try block.

When a function is exited due to an exception, the compiler guarantees that the local objects are properly destroyed. During stack unwinding, the memory used by local objects is freed and destructors for local objects of class type are run.

Destructors are often executed during stack unwinding. When destructors are executing, the exception has been raised but not yet handled. While stack unwinding is in progress for an exception, a destructor that throws another exception of its own that it does not also handle, causes the library terminate function is called. Ordinarily, terminate calls abort , forcing an abnormal exit from the entire program. Because terminate ends the program, it is usually a very bad idea for a destructor to do anything that might cause an exception. The standard library types all guarantee that their destructors will not raise an exception.

Unlike destructors, it is often the case that something done inside a constructor might throw an exception. If an exception occurs while constructing an object, then the object might be only partially constructed. Some of its members might have been initialized, and others might not have been initialized before the exception occurs. Even if the object is only partially constructed, we are guaranteed that the constructed members will be properly destroyed. Similarly, an exception might occur when initializing the elements of an array or other container type. Again, we are guaranteed that the constructed elements will be destroyed.

uncaught exceptions terminate the program.

3. Catching an Exception

The exception specifier in a catch clause looks like a parameter list that contains exactly one parameter. The exception specifier is a type name followed by an optional parameter name. During the search for a matching catch , the catch that is found is not necessarily the one that matches the exception best. Instead, the catch that is selected is the first catch found that can
handle the exception. As a consequence, in a list of catch clauses, the most specialized catch must appear first. Most conversions are not allowed the types of the exception and the catch specifier must match exactly with only a few possible differences:

  • Conversions from non const to const are allowed. That is, a throw of a non const object can match a catch specified to take a const reference.
  • Conversions from derived type to base type are allowed.
  • An array is converted to a pointer to the type of the array; a function is converted to the appropriate pointer to function type.

In particular, neither the standard arithmetic conversions nor conversions defined for class types are permitted.

When a catch is entered, the catch parameter is initialized from the exception object. As with a function parameter, the exception-specifier type might be a reference. The exception object itself is a copy of the object that was thrown. Whether the exception object is copied again into the catch site depends on the exception-specifier type. Like a parameter declaration, an exception specifier for a base class can be used to catch an exception object of a derived type. Usually, a catch clause that handles an exception of a type related by inheritance ought to define its parameter as a reference. Because objects (as opposed to references) are not polymorphic.

Because catch clauses are matched in the order in which they appear, programs that use exceptions from an inheritance hierarchy must order their catch clauses so that handlers for a derived type occurs before a catch for its base type.

4. Rethrow

A catch can pass the exception out to another catch further up the list of function calls by rethrowing the exception. A rethrow is a throw that is not followed by a type or an expression: throw; An empty throw can appear only in a catch or in a function called (directly or indirectly) from a catch. If an empty throw is encountered when a handler is not active, terminate is called.

The exception that is thrown is the original exception object, not the catch parameter. When a catch parameter is a base type, then we cannot know the actual type thrown by a rethrow expression. That type depends on the dynamic type of the exception object, not the static type of the catch parameter. In general, a catch might change its parameter. If, after changing its parameter, the catch rethrows the exception, then those changes will be propagated only if the exception specifier is a reference:

catch (my_error &eObj) { // specifier is a reference type
eObj.status = severeErr; // modifies the exception object
throw; // the status member of the exception object is severeErr
} catch (other_error eObj) { // specifier is a nonreference type
eObj.status = badErr; // modifies local copy only
throw; // the status member of the exception rethrown is unchanged
}

5. The Catch-All Handler

A catch(…) is often used in combination with a rethrow expression. The catch does whatever local work can be done and then rethrows the exception:

void manip() {
try {
// actions that cause an exception to be thrown
}
catch (...) {
// work to partially handle the exception
throw;
}
}

6. Function Try Blocks and Constructors

Constructor initializers are processed before the constructor body is entered. A catch clause inside the constructor body cannot handle an exception that might occur while processing a constructor initializer. To handle an exception from a constructor initializer, we must write the constructor as a function try block.

template <class T> Handle<T>::Handle(T *p)
try : ptr(p), use(new size_t(1))
{
// empty function body
}
catch(const std::bad_alloc &e)
{
handle_out_of_memory(e);
}

c++ primer笔记16: pointer to function

1. Pointers to Functions

A function’s type is determined by its return type and its parameter list. A function’s name is not part of its type:

// pf points to function returning bool that takes two const string references
bool (*pf)(const string &, const string &);

// The parentheses around *pf are necessary
// declares a function named pf that returns a bool*
bool *pf(const string &, const string &);

typedef bool (*cmpFcn)(const string &, const string &);

2. Initializing and Assigning Pointers to Functions

When we use a function name without calling it, the name is automatically treated as a pointer to a function.

cmpFcn pf1 = lengthCompare;  \\  automatically converted to a function pointer
cmpFcn pf2 = &lengthCompare;

string::size_type sumLength(const string&, const string&);
bool cstringCompare(char*, char*);
// pointer to function returning bool taking two const string&
cmpFcn pf;
pf = sumLength; // error: return type differs

A pointer to a function can be used to call the function to which it refers. We can use the pointer directly there is no need to use the dereference operator to call the function

cmpFcn pf = lengthCompare;
lengthCompare("hi", "bye"); // direct call
pf("hi", "bye"); // equivalent call: pf1 implicitly dereferenced
(*pf)("hi", "bye"); // equivalent call: pf1 explicitly dereferenced

3. Function Pointer Parameters

// third parameter is a function type and is automatically treated as a pointer to function
void useBigger(const string &, const string &,
bool(const string &, const string &))
;


// equivalent declaration: explicitly define the parameter as a pointer to function
void useBigger(const string &, const string &,
bool (*)(const string &, const string &))
;

4. Returning a Pointer to Function

int (*ff(int))(int*, int), The best way to read function pointer declarations is from the inside out, starting with the name being declared. Typedefs can make such declarations considerably easier to read:

// PF is a pointer to a function returning an int, taking an int* and an int
typedef int (*PF)(int*, int);
PF ff(int); // ff returns a pointer to function

We can define a parameter as a function type. A function return type must be a pointer to function; it cannot be a function. An argument to a parameter that has a function type is automatically converted to the corresponding pointer to function type. The same conversion does not happen when returning a function:

// func is a function type, not a pointer to function!
typedef int func(int*, int);
void f1(func); // ok: f1 has a parameter of function type
func f2(int); // error: f2 has a return type of function type
func *f3(int); // ok: f3 returns a pointer to function type

c++ primer笔记15

1. *** Overloading and Function Templates ***

A function template can be overloaded: We can define multiple function templates with the same name but differing numbers or types of parameters. We also can define ordinary nontemplate functions with the same name as a function template. However, overloaded function templates may lead to ambiguities. The steps used to resolve a call to an overloaded function in which there are both ordinary functions and function templates are as follows:

  • Build the set of candidate functions for this function name, including:
    a. Any ordinary function with the same name as the called function.
    b. Any function-template instantiation for which template argument deduction finds template arguments that match the function arguments used in the call.
  • Determine which, if any, of the ordinary functions are viable. Each template instance in the candidate set is viable, because template argument deduction ensures that the function could be called.
  • Rank the viable functions by the kinds of conversions, if any, required to make the call, remembering that the conversions allowed to call an instance of a template function are limited.
    a. If only one function is selected, call this function.
    b. If the call is ambiguous, remove any function template instances from the set of viable functions.
  • Rerank the viable functions excluding the function template instantiations.
    a. If only one function is selected, call this function.
    b. Otherwise, the call is ambiguous.

2. An Example of Function-Template Matching

template <typename T> int compare(const T&, const T&);
template <class U, class V> int compare(U, U, V);
int compare(const char*, const char*);

// calls compare(const T&, const T&) with T bound to int
compare(1, 0);

// calls compare(U, U, V), with U and V bound to vector<int>::iterator
vector<int> ivec1(10), ivec2(20);
compare(ivec1.begin(), ivec1.end(), ivec2.begin());
int ia1[] = {0,1,2,3,4,5,6,7,8,9};

// calls compare(U, U, V) with U bound to int* and V bound to vector<int>::iterator
compare(ia1, ia1 + 10, ivec1.begin());

// calls the ordinary function taking const char* parameters
const char const_arr1[] = "world", const_arr2[] = "hi";
compare(const_arr1, const_arr2);

// calls the ordinary function taking const char* parameters
char ch_arr1[] = "world", ch_arr2[] = "hi";
compare(ch_arr1, ch_arr2);

再进行分析前,先看一个小例子:

int compare(const char (&v1)[3], const char (&v2)[3]);
int compare(const char *v1, const char *v2);
int main(int argc, char **argv)
{
const char array1[] = "wo";
const char array2[] = "hi";

compare(array1, array2);
}

error: call of overloaded ‘compare(const char [3], const char [3])’ is ambiguous
note: candidates are: int compare(const char (&)[3], const char (&)[3])
note: int compare(const char*, const char*)
// 即编译器认为这个两个函数是同样好的。

compare(const_arr1, const_arr2):它的candidates只有int compare(const char*, const char*). 因为模板函数int compare(const T&, const T&)中的参数类型是引用,const_arr1的类型是const char[6],const_arr2的类型是const char[3],两个实参的类型不同,所以不能使用模板进行实例化。
compare(const_arr1, const_arr1):它的candidates有int compare(const char*, const char*)int compare(const char (&)[6], const char (&)[6],由前面的例子可知,它们两个是一样好的,但是依据匹配规则,如果出现ambiguous的情况,则剔除由函数模板实例化而来的函数。
compare(ch_arr1, ch_arr2): 与compare(const_arr1, const_arr2)类似。

3. Conversions and Overloaded Function Templates

Let’s look at two examples of why it is hard to design overloaded functions that work properly when there are both template and nontemplate versions in the overload set. First, consider a call to compare using pointers instead of the arrays themselves:

char *p1 = ch_arr1, *p2 = ch_arr2;
compare(p1, p2);

This call matches the template version! Ordinarily, we expect to get the same function whether we pass an array or a pointer to an element to that array. In this case, however, the function template is an exact match for the call, binding char to T. The plain version still requires a conversion from char to const char* , so the function template is preferred. 此处没有理解啊???

Another change that has surprising results is what happens if the template version of compare has a parameter of type T instead of a const reference to T:

template <typename T> int compare2(T, T);

// calls compare(T, T) with T bound to char*
compare(ch_arr1, ch_arr2);
// calls compare(T, T) with T bound to char*
compare(p1, p2);
// calls the ordinary function taking const char*
parameters compare(const_arr1, const_arr2);
const char *cp1 = const_arr1, *cp2 = const_arr2;
// calls the ordinary function taking const char* parameters
compare(cp1, cp2);

In these cases, the plain function and the function template are exact matches. As always, when the match is equally good, the nontemplate version is preferred.

It is hard to design overloaded function sets involving both function templates and nontemplate functions. Because of the likelihood of surprise to users of the functions, it is almost always better to define a function-template specialization than to use a nontemplate version.

c++ primer笔记14

1. Specializing a Class Template

Our Queue class has a problem similar to the one in compare when used with C-style strings. In this case, the problem is in the push function. That function copies the value it’s given to create a new element in the Queue. By default, copying a C-style character string copies only the pointer, not the characters.

template<> class Queue<const char*> {
public:
// no copy control: Synthesized versions work for this class
// similarly, no need for explicit default constructor either
void push(const char*);
void pop() {real_queue.pop();}
bool empty() const {return real_queue.empty();}
// Note: return type does not match template parameter type
std::string front() {return real_queue.front();}
const std::string &front() const
{return real_queue.front();}
private:
Queue<std::string> real_queue; // forward calls to real_queue
};

It is worth noting that a specialization may define completely different members than the template itself. If a specialization fails to define a member from the template, that member may not be used on objects of the specilization type. The member definitions of the class template are not used to create the definitions for the members of an explicit specialization.

A class template specialization ought to define the same interface as the template it specializes. Doing otherwise will surprise users when they attempt to use a member that is not defined.

2. Class Specialization Definition

When a member is defined outside the class specialization, it is not preceded by the tokens template<>.

void Queue<const char*>::push(const char* val)
{
return real_queue.push(val);
}

3. Specializing Members but Not the Class

If we look a bit more deeply at our class, we can see that we can simplify our code: Rather than specializing the whole template, we can specialize just the push and pop members. We’ll specialize push to copy the character array and pop to free the memory we used for that copy:

template <>
void Queue<const char*>::push(const char *const &val)
{
// allocate a new character array and copy characters from val
char* new_item = new char[strlen(val) + 1];
strncpy(new_item, val, strlen(val) + 1);
// store pointer to newly allocated and initialized element
QueueItem<const char*> *pt = new QueueItem<const char*>(new_item);
// put item onto existing queue
if (empty())
head = tail = pt; // queue has only one element
else {
tail->next = pt; // add new element to end of queue
tail = pt;
}
}

template <>
void Queue<const char*>::pop()
{
// remember head so we can delete it
QueueItem<const char*> *p = head;
delete head->item; // delete the array allocated in push
head = head->next; // head now points to next element
delete p; // delete old head element
}

4. Specialization Declarations

Member specializations are declared just as any other function template specialization. They must start with an empty template parameter list:

// push and pop specialized for const char*
template <>
void Queue<const char*>::push(const char* const &);
template <> void Queue<const char*>::pop();

5. *** Class-Template Partial Specializations ***

If a class template has more than one template parameter, we might want to specialize some but not all of the template parameters. We can do so using a class template partial specialization:

template <class T1, class T2>
class some_template
{
public:
void print()
{
cout << "hello" << endl;
}
};

// partial specialization: fixes T2 as int and allows T1 to vary
template <class T1>
class some_template<T1, int>
{
};

As with any other class template, a partial specialization is instantiated implicitly when used in a program:

some_template<int, string> foo; // uses template
some_template<string, int> bar; // uses partial specialization

The definition of a partial specialization is completely disjointed from the definition of the generic template. The partial specialization may have a completely different set of members from the generic class template. The generic definitions for the members of a class template are never used to instantiate the members of the class template partial specialization.

Demo<double, int> d;
d.print();

// error: ‘class Demo<double, int>’ has no member namedprint

c++ primer笔记13

1. Defining the Generic Handle Class

To create a Handle , a user will be expected to pass the address of a dynamically allocated object of the type (or a type
derived from that type) managed by the Handle . From that point on, the Handle will “own” the given object. In particular, the Handle class will assume responsibility for deleting that object once there are no longer any Handle s attached to it. Once an object is bound to a Handle, the user must not delete that object.

template <class T> class Handle {
public:
// unbound handle
Handle(T *p = 0): ptr(p), use(new size_t(1)) { }
// overloaded operators to support pointer behavior
T& operator*();
T* operator->();
const T& operator*() const;
const T* operator->() const;
// copy control: normal pointer behavior, but last Handle deletes the object
Handle(const Handle& h): ptr(h.ptr), use(h.use)
{ ++*use; }
Handle& operator=(const Handle&);
~Handle() { rem_ref(); }
private:
T* ptr; // shared object
size_t *use; // count of how many Handle spointto *ptr
void rem_ref()
{ if (--*use == 0) { delete ptr; delete use; } }

};

template <class T>
inline Handle<T>& Handle<T>::operator=(const Handle &rhs)
{
++*rhs.use; // protect against self-assignment
rem_ref(); // decrement use count and delete pointers if needed
ptr = rhs.ptr;
use = rhs.use;
return *this;
}

2. Using the Handle

We intend this class to be used by other classes in their internal implementations. However, as an aid to understanding how the Handle class works, we’ll look at a simpler example first. This example illustrates the behavior of the Handle by allocating an int and binding a Handle to that newly allocated object:

{ // new scope
// user allocates but must not delete the object to which the Handle is attached
Handle<int> hp(new int(42));
{ // new scope
Handle<int> hp2 = hp; // copies pointer; use count incremented
cout << *hp << " " << *hp2 << endl; // prints 42 42
*hp2 = 10; // changes value of shared underlying int
} // hp2 goes out of scope; use count is decremented
cout << *hp << endl; // prints 10
} // hp goes out of scope; its destructor deletes the int

As an example of using Handle in a class implementation, we might reimplement our Sales_item class. This version of the class defines the same interface, but we can eliminate the copy-control members by replacing the pointer to Item_base by a Handle:

class Sales_item {
public:
// default constructor: unbound handle
Sales_item(): h() { }
// copy item and attach handle to the copy
Sales_item(const Item_base &item): h(item.clone()) { }
// no copy control members: synthesized versions work
// member access operators: forward their work to the Handle class
const Item_base& operator*() const { return *h; }
const Item_base* operator->() const
{ return h.operator->(); }
private:
Handle<Item_base> h; // use-counted handle
};

3. *** Template Specializations ***

It is not always possible to write a single template that is best suited for every possible template argument with which the template might be instantiated. In some cases, the general template definition is simply wrong for a type. The general definition might not compile or might do the wrong thing. At other times, we may be able to take advantage of some specific knowledge
about a type to write a more efficient function than the one that is instantiated from the template. Let’s look again at our compare function template. If we call this template definition on two const char* arguments, the function compares the pointer values.

template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}

4. Specializing a Function Template

A template spacialization is a separate definition in which the actual type(s) or value(s) of one or more template parameter(s) is (are) specified. The declaration for the specialization must match that of the corresponding template.

// special version of compare to handle C-style character strings
template <>
int compare<const char*>(const char* const &v1,
const char* const &v2)
{
return strcmp(v1, v2);
}

Now when we call compare , passing it two character pointers, the compiler will call our specialized version. It will call the generic version for any other argument types (including plain char*,也就是说要完全匹配):

const char *cp1 = "world", *cp2 = "hi";
int i1, i2;
compare(cp1, cp2); // calls the specialization
compare(i1, i2); // calls the generic version instantiated with int

As with any function, we can declare a function template specialization without defining it. A template specialization declaration looks like the definition but omits the function body. If the template arguments can be inferred from the function parameter
list, there is no need to explicitly specify the template arguments:

// error: invalid specialization declarations
// missing template<>
int compare<const char*>(const char* const&, const char* const&);

// error: function parameter list missing
template<> int compare<const char*>;

// ok: explicit template argument const char* deduced from parameter types
template<> int compare(const char* const&, const char* const&);

5. Function Overloading versus Template Specializations

Omitting the empty template parameter list, template<> , on a specialization may have surprising effects. If the specialization syntax is missing, then the effect is to declare an overloaded nontemplate version of the function:

// generic template definition
template <class T>
int compare(const T& t1, const T& t2) { /* ... */ }
// OK: ordinary function declaration
int compare(const char* const&, const char* const&);

The definition of compare does not define a template specialization. Instead, it declares an ordinary function with a return type and a parameter list that could match those of a template instantiation.

We’ll look at the interaction of overloading and templates in more detail in the next section. For now, what’s important to know is that when we define a nontemplate function, normal conversions are applied to the arguments. When we specialize a template, conversions are not applied to the argument types. In a call to a specialized version of a template, the argument type(s) in the call must match the specialized version function parameter type(s) exactly. If they don’t, then the compiler will instantiate an instantiation for the argument(s) from the template definition.

If a program consists of more than one file, the declaration for a template specialization must be visible in every file in which the specialization is used. As with other function declarations, declarations for template specializations should be included in a header file. That header should then be included in every source file that uses the specialization.

6. Ordinary Scope Rules Apply to Specializations

Before we can declare or define a specialization, a declaration for the template that it specializes must be in scope. Similarly, a declaration for the specialization must be in scope before that version of the template is called:

// define the general compare template
template <class T>
int compare(const T& t1, const T& t2) { /* ... */ }

int main() {
// uses the generic template definition
int i = compare("hello", "world");
// ...
}

// invalid program: explicit specialization after call
template<>
int compare<const char*>(const char* const& s1, const char* const& s2)
{ /* ... */ }

c++ primer笔记12

1. Member Templates

Any class (template or otherwise) may have a member that is itself a class or function template. Such members are referred to as member templates. One example of a member template is the assign member of the standard containers. The version assign that takes two iterators uses a template parameter to represent the type of its iterator parameters. Another member template example is the container constructor that takes two iterators. This constructor and the assign member allow containers to be built from sequences of different but compatible element types and/or different container types.

Consider the Queue copy constructor: It takes a single parameter that is a reference to a Queue . If we wanted to create a Queue by copying elements from a vector, we could not do so; there is no conversion from vector to Queue . Similarly, if we wanted to copy elements from a Queue into a Queue, we could not do so. Again, even though we can convert a short
to an int , there is no conversion from Queue to Queue. The same logic applies to the Queue assignment operator, which also takes a parameter of type Queue&.

The problem is that the copy constructor and assignment operator fix both the container and element type. We’d like to define a constructor and an assign member that allow both the container and element type to vary. When we need a parameter type to vary, we need to define a function template. In this case, we’ll define the constructor and assign member to take a pair ofiterators that denote a range in some other sequence. These functions will have a single template type parameter that represents an iterator type.

2. Defining a Member Template

template <class Type> class Queue {
public:
// construct a Queue from a pair of iterators on some sequence
template <class It>
Queue(It beg, It end):
head(0), tail(0) { copy_elems(beg, end); }
// replace current Queue by contents delimited by a pair of iterators
template <class Iter> void assign(Iter, Iter);
// rest of Queue class as before
private:
// version of copy to be used by assign to copy elements from iterator range
template <class Iter> void copy_elems(Iter, Iter);
}

When we define a member template outside the scope of a class template, we must include both template parameter lists:

template <class T> template <class Iter>
void Queue<T>::assign(Iter beg, Iter end)
{
destroy(); // remove existing elements in this Queue
copy_elems(beg, end); // copy elements from the input range
}

When a member template is a member of a class template, then its definition must include the class-template parameters as well as its own template parameters. The class-template parameter list comes first, followed by the member’s own template parameter list. The definition of assign starts with template <class T> template <class Iter>.

3. Member Templates and Instantiation

Like any other member, a member template is instantiated only when it is used in a program. Member templates have two kinds of
template parameters: Those that are defined by the class and those defined by the member template itself. The class template parameters are fixed by the type of the object through which the function is called. The template parameters defined by the member act like parameters of ordinary function templates. These parameters are resolved through normal template argument deduction.

short a[4] = { 0, 3, 6, 9 };
// instantiates Queue<int>::Queue(short *, short *)
Queue<int> qi(a, a + 4); // copies elements from a into qi
vector<int> vi(a, a + 4);
// instantiates Queue<int>::assign(vector<int>::iterator,
// vector<int>::iterator)
qi.assign(vi.begin(), vi.end());

4. The Complete Queue Class

// declaration that Queue is a template needed for friend declaration in QueueItem
template <class Type> class Queue;
// function template declaration must precede friend declaration in QueueItem
template <class T>
std::ostream& operator<<(std::ostream&, const Queue<T>&);

template <class Type> class QueueItem {
friend class Queue<Type>;
// needs access to item and next
friend std::ostream& // defined on page 659
operator<< <Type> (std::ostream&, const Queue<Type>&);
// private class: no public section
QueueItem(const Type &t): item(t), next(0) { }
Type item; // value stored in this element
QueueItem *next; // pointer to next element in the Queue
};

template <class Type> class Queue {
// needs access to head
friend std::ostream& // defined on page 659
operator<< <Type> (std::ostream&, const Queue<Type>&);
public:
// empty Queue
Queue(): head(0), tail(0) { }
// construct a Queue from a pair of iterators on some sequence
template <class It>
Queue(It beg, It end):
head(0), tail(0) { copy_elems(beg, end); }
// copy control to manage pointers to QueueItems in the Queue
Queue(const Queue &Q): head(0), tail(0)
{ copy_elems(Q); }
Queue& operator=(const Queue&); // left as exercise for the reader
~Queue() { destroy(); }
// replace current Queue by contents delimited by a pair of iterators
template <class Iter> void assign(Iter, Iter);
// return element from head of Queue
// unchecked operation: front on an empty Queue is undefined
Type& front() { return head->item; }
const Type &front() const { return head->item; }
void push(const Type &);// defined on page 652
void pop(); // defined on page 651
bool empty() const { // true if no elements in the Queue
return head == 0;
}
private:
QueueItem<Type> *head; // pointer to first element in Queue
QueueItem<Type> *tail; // pointer to last element in Queue
// utility functions used by copy constructor, assignment, and destructor
void destroy(); // defined on page 651
void copy_elems(const Queue&); // defined on page 652
// version of copy to be used by assign to copy elements from iterator range
// defined on page 662
template <class Iter> void copy_elems(Iter, Iter);
};

// Inclusion Compilation Model: include member function definitions as well
#include "Queue.cc"

5. static Members of Class Templates

template <class T> class Foo {
public:
static std::size_t count() { return ctr; }
// other interface members
private:
static std::size_t ctr;
// other implementation members
};

// Each object shares the same Foo<int>::ctrand Foo<int>::count members
Foo<int> fi, fi2, fi3;

// has static members Foo<string>::ctrand Foo<string>::count
Foo<string> fs;

Foo<int> fi, fi2; // instantiates Foo<int> class
size_t ct = Foo<int>::count(); // instantiates Foo<int>::count
ct = fi.count(); // ok: uses Foo<int>::count
ct = fi2.count(); // ok: uses Foo<int>::count
ct = Foo::count(); // error: which template instantiation?

As with any other static data member, there must be a definition for the data member that appears outside the class. In the case of a class template static , the member definition must inidicate that it is for a class template:

template <class T>
size_t Foo<T>::ctr = 0; // define and initialize ctr

c++ primer笔记11

1. Class-Template Member Functions

The definition of a member function of a class template has the following form:

  • It must start with the keyword template followed by the template parameter list for the class.
  • It must indicate the class of which it is a member.
  • The class name must include its template parameters.

template <class T> ret-type Queue<T>::member-name

template <class Type>
void Queue<Type>::copy_elems(const Queue &orig)
{
for (QueueItem<Type> *pt = orig.head; pt; pt = pt->next)
push(pt->item);
}

Member functions of class templates are themselves function templates. Like any other function template, a member function of a class template is used to generate instantiations of that member. Unlike other function templates, the compiler does not perform template-argument deduction when instantiating class template member functions. Instead, the template parameters of a class template member function are determined by the type of the object on which the call is made. For example, when we call the push member of an object of type Queue , the push function that is instantiated is void Queue<int>::push(const int &val).

Queue<int> qi; // instantiates class Queue<int>
short s = 42;
int i = 42;
// ok: s converted to int and passed to push
qi.push(s); // instantiates Queue<int>::push(const int&)
qi.push(i); // uses Queue<int>::push(const int&)
f(s); // instantiates f(const short&)
f(i); // instantiates f(const int&)

Member functions of a class template are instantiated only for functions that are used by the program. If a function is never used, then that member function is never instantiated. This behavior implies that types used to instantiate a template need to meet only the requirements of the operations that are actually used. When we define an object of a template type, that definition causes the class template to be instantiated. Defining an object also instantiates whichever constructor was used to initialize the object, along with any members called by that constructor:

// instantiates Queue<string> class and Queue<string>::Queue()
Queue<string> qs;
qs.push("hello"); // instantiates Queue<string>::push

template <class Type> void Queue<Type>::push(const Type &val)
{
// allocate a new QueueItem object
QueueItem<Type> *pt = new QueueItem<Type>(val);
// put item onto existing queue
if (empty())
head = tail = pt; // the queue now has only one element
else {
tail->next = pt; // add new element to end of the queue
tail = pt;
}
}

in turn instantiates the companion QueueItem class and its constructor. The QueueItem members in Queue are pointers. Defining a pointer to a class template doesn’t instantiate the class; the class is instantiated only when we use such a pointer. Thus, QueueItem is not instantiated when we create a Queue object. Instead, the QueueItem class is instanatiated when a Queue member such as front, push, or pop is used.

2. Template Arguments for Nontype Parameters

template <int hi, int wid>
class Screen {
public:
// template nontype parameters used to initialize data members
Screen(): screen(hi * wid, '#'), cursor (0),
height(hi), width(wid) { }
// ...
private:
std::string screen;
std::string::size_type cursor;
std::string::size_type height, width;
};

// define Screen() outside the class
template<int hi, int wid>
Screen<hi, wid>::Screen<hi, wid>() : screen(hi * wid, '#'), cursor (0),
height(hi), width(wid) { }

Screen<24,80> hp2621;

Nontype template arguments must be compile-time constant expressions.

3. Friend Declarations in Class Templates

(1) A friend declaration for an ordinary nontemplate class or function, which grants friendship to the specific named class or function.

template <class Type> class Bar {
// grants access to ordinary, nontemplate class and function
friend class FooBar;
friend void fcn();
// ...
};

(2) A friend declaration for a class template or function template, which grants access to all instances of the friend.

template <class Type> class Bar {
// grants access to Foo1 or templ_fcn1 parameterized by any type
template <class T> friend class Foo1;
template <class T> friend void templ_fcn1(const T&);
// ...
};

(3) A friend declaration that grants access only to a specific instance of a class or function template.

template <class T> class Foo2;
template <class T> void templ_fcn2(const T&);
template <class Type> class Bar {
// grants access to a single specific instance parameterized by char*
friend class Foo2<char*>;
friend void templ_fcn2<char*>(char* const &);
// ...
};

More common are friend declarations of the following form:

template <class T> class Foo3;
template <class T> void templ_fcn3(const T&);
template <class Type> class Bar {
// each instantiation of Bar grants access to the
// version of Foo3 or templ_fcn3 instantiated with the same type
friend class Foo3<Type>;
friend void templ_fcn3<Type>(const Type&);
// ...
};

4. Declaration Dependencies

When we grant access to all instances of a given template, there need not be a declaration for that class or function template in scope. Essentially, the compiler treats the friend declaration as a declaration of the class or function as well. When we want to restrict friendship to a specific instantiation, then the class or function must have been declared before it can be used in a friend declaration:

template <class T> class A;
template <class T> class B {
public:
friend class A<T>; // ok: A is known to be a template
friend class C; // ok: C must be an ordinary, nontemplate class
template <class S> friend class D; // ok: D is a template
friend class E<T>; // error: E wasn't declared as a template
friend class F<int>; // error: F wasn't declared as a template
};

5. Making a Function Template a Friend

// function template declaration must precede friend declaration in QueueItem
template <class T>
std::ostream& operator<<(std::ostream&, const Queue<T>&);

template <class T> class Queue;

template <class Type> class QueueItem {
friend class Queue<Type>;
// needs access to item and next
friend std::ostream&
operator<< <Type> (std::ostream&, const Queue<Type>&);
// ...
};

template <class Type> class Queue {
// needs access to head
friend std::ostream&
operator<< <Type> (std::ostream&, const Queue<Type>&);
};

c++ primer笔记10

1. Template Compilation Models

When the compiler sees a template definition, it does not generate code immediately. The compiler produces type-specific instances of the template only when it sees a use of the template, such as when a function template is called or an object of a class template is defined.

Ordinarily, when we call a function, the compiler needs to see only a declaration for the function. Similarly, when we define an object of class type, the class definition must be available, but the definitions of the member functions need not be present. As a result, we put class definitions and function declarations in header files and definitions of ordinary and class-member functions in source files.

Templates are different: To generate an instantiation, the compiler must have access to the source code that defines the template. When we call a function template or a member function of a class template, the compiler needs the function definition. It needs the code we normally put in the source files.

Standard C++ defines two models for compiling template code: inclusion compilation model and separate compilation model . Class definitions and function declarations go in header files, and function and member definitions go in source files. The two models differ in how the definitions from the source files are made available to the compiler.

2. Inclusion Compilation Model

In the inclusion compilation model , the compiler must see the definition for any template that is used. Typically, we make the definitions available by adding a #include directive to the headers that declare function or class templates. That #include brings in the source file(s) that contain the associated definitions:

// header file utlities.h
#ifndef UTLITIES_H
#define UTLITIES_H
template <class T> int compare(const T&, const T&);
#include "utilities.cc" // get the definitions for compare etc.
#endif

// implemenatation file utlities.cc
template <class T> int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}

Some, especially older, compilers that use the inclusion model may generate multiple instantiations. If two or more separately compiled source files use the same template, these compilers will generate an instantiation for the template in each file. Ordinarily, this approach implies that a given template will be instantiated more than once. At link time, or during a prelink
phase, the compiler selects one instantiation, discarding the others. In such cases, compile-time performance can be significantly degraded if there are a lot of files that instantiate the same template. Such compilers often support mechanisms that avoid the compile-time overhead implicit in multiple instantiations of the same template. If compile time for programs using templates is too burdensome, consult your compiler’s user’s guide to see what support your compiler offers to avoid redundant instantiations.

3. Separate Compilation Model

In the separate compilation model , the compiler keeps track of the associated template definitions for us. However, we must tell the compiler to remember a given template definition. We use the export keyword to do so. A template may be defined as exported only once in a program. The declaration for this function template, should, as usual, be put in a header. The declaration must not specify export.

// **.h
template<typename Type> Type sum(Type t1, Type t2);

// **.cpp
export template <typename Type>
Type sum(Type t1, Type t2) {}

Using export on a class template is a bit more complicated. As usual, the class declaration must go in a header file. The class body in the header should not use the export keyword. If we used export in the header, then that header could be used by only one source file in the program.

// class template header goes in shared header file
template <class Type> class Queue { ... };
// Queue.ccimplementation file declares Queue as exported
export template <class Type> class Queue;
#include "Queue.h"
// Queue member definitions

The members of an exported class are automatically declared as exported. It is also possible to declare individual members of a class template as exported. In this case, the keyword export is not specified on the class template itself. It is specified only on the specific member definitions to be exported. The definition of exported member functions need not be visible when the member is used. The definitions of any nonexported member must be treated as in the inclusion model: The definition should be placed inside the header that defines the class template.

4. Class Template Members


Class QueueItem is a private classit has no public interface. We intend this class to be used to implement Queue and have not built it for general use. Hence, it has no public members. We’ll need to make class Queue a friend of QueueItem so that its members can access the members of QueueItem.

Inside the scope of a class template, we may refer to the class using its unqualified name.

template <class Type> class QueueItem {
// private class: no public section
QueueItem(const Type &t): item(t), next(0) { }
Type item; // value stored in this element
QueueItem *next; // pointer to next element in the Queue
};

template <class Type>
class Queue
{
public:
// empty Queue
Queue(): head(0), tail(0) { }
// copy control to manage pointers to QueueItems in the Queue
Queue(const Queue &Q): head(0), tail(0)
{ copy_elems(Q); }
Queue& operator=(const Queue&);
~Queue() { destroy(); }
// return element from head of Queue
// unchecked operation: front on an empty Queue is undefined
Type& front() { return head->item; }
const Type &front() const { return head->item; }
void push(const Type &); // add element to back of Queue
void pop (); // remove element from head of Queue
bool empty () const { // true if no elements in the Queue
return head == 0;
}

private:
QueueItem<Type> *head; // pointer to first element in Queue
QueueItem<Type> *tail; // pointer to last element in Queue
// utility functions used by copy constructor, assignment, and destructor
void destroy(); // delete all the elements
void copy_elems(const Queue&); // copy elements from parameter
};

Ordinarily, when we use the name of a class template, we must specify the template parameters. There is one exception to this rule: Inside the scope of the class itself, we may use the unqualified name of the class template. For example, in the declarations of the default and copy constructor the name Queue is a shorthand notation that stands for Queue. Hence,
the copy constructor definition is really equivalent to writing:

Queue<Type>(const Queue<Type> &Q): head(0), tail(0)
{ copy_elems(Q); }

The compiler performs no such inference for the template parameter(s) for other templates used within the class. Hence, we must specify the type parameter when declaring pointers to the companion QueueItem class:

QueueItem<Type> *head; // pointer to first element in Queue
QueueItem<Type> *tail; // pointer to last element in Queue

c++ primer笔记9

1. Instantiation

A template is a blueprint; it is not itself a class or a function. The compiler uses the template to generate type-specific versions of the specified class or function. The process of generatng a type-specific instance of a template is known as instantiation. The term reflects the notion that a new “instance” of the template type or function is created.

A template is instantiated when we use it. A class template is instantiated when we refer to the an actual template class type, and a function template is instantiated when we call it or use it to initialize or assign to a pointer to function.

When we want to use a class template, we must always specify the template arguments explicitly. When we use a function template, the compiler will usually infer the template arguments for us.

2.Template Argument Deduction

To determine which functions to instantiate, the compiler looks at each argument. If the corresponding parameter was declared with a type that is a type parameter, then the compiler infers the type of the parameter from the type of the argument. The process of determining the types and values of the template arguments from the type of the function arguments is called template argument deduction. note: Multiple Type Parameter Arguments Must Match Exactly.

3.Limited Conversions on Type Parameter Arguments

short s1, s2;
int i1, i2;
compare(i1, i2); // ok: instantiate compare(int, int)
compare(s1, s2); // ok: instantiate compare(short, short)

In general, arguments are not converted to match an existing instantiation; instead, a new instance is generated. There are only two kinds of conversions that the compiler will perform rather than generating a new instantiation:

  • const conversions: A function that takes a reference or pointer to a const can be called with a reference or pointer to non const object, respectively, without generating a new instantiation. If the function takes a nonreference type, then const is ignored on either the parameter type or the argument. That is, the same instantiation will be used whether we pass a const or non const object to a function defined to take a nonreference type.
  • array or function to pointer conversions: If the template parameter is not a reference type, then the normal pointer conversion will be applied to arguments of array or function type. An array argument will be treated as a pointer to its first element, and a function argument will be treated as a pointer to the function’s type.
template <typename T> T fobj(T, T); // arguments are copied
template <typename T>
T fref(const T&, const T&); // reference arguments
string s1("a value");
const string s2("another value");
fobj(s1, s2); // ok: calls f(string, string), const is ignored
fref(s1, s2); // ok: non const object s1 converted to const reference
int a[10], b[42];
fobj(a, b); // ok: calls f(int*, int*)
fref(a, b); // error: array types don't match; arguments aren't converted to pointers

4.Template Argument Deduction and Function Pointers

template <typename T> int compare(const T&, const T&);
// pf1 points to the instantiation int compare (const int&, const int&)
int (*pf1) (const int&, const int&) = compare;

It is an error if the template arguments cannot be determined from the function pointer type.

void func(int(*) (const string&, const string&));
void func(int(*) (const int&, const int&));
func(compare); // error: which instantiation of compare?

5.Function-Template Explicit Arguments

In some situations, it is not possible to deduce the types of the template arguments. In such situations, it is necessary to override the template argument deduction mechanism and explicitly specify the types or values to be used for the template
parameters.

Consider the following problem. We wish to define a function template called sum that takes arguments of two differnt types. We’d like the return type to be large enough to contain the sum of two values of any two types passed in any order. How can we do that? How should we specify sum’s return type?

// T or U as the returntype?
template <class T, class U> ??? sum(T, U);

// neither T nor U works as return typ
sum(3, 4L); // second type is larger; want U sum(T, U)
sum(3L, 4); // first type is larger; want T sum(T, U)

An alternative way to specify the return type is to introduce a third template parameter that must be explicitly specified by our caller. We supply an explicit template argument to a call in much the same way that we define an instance of a class template.

template <class T1, class T2, class T3>
T1 sum(T2, T3);

// ok T1 explicitly specified; T2 and T3 inferred from argument types
long val3 = sum<long>(i, lng); // ok: calls long sum(int, long)

Explicit template argument(s) are matched to corresponding template parameter(s) from left to right; the first template argument is matched to the first template parameter. An explicit template argument may be omitted only for the trailing (rightmost) parameters, assuming these can be deduced from the function parameters. If our sum function had been written as

template <class T1, class T2, class T3>
T3 alternative_sum(T2, T1);

// error: can't infer initial template parameters
long val3 = alternative_sum<long>(i, lng);
// ok: All three parameters explicitly specified
long val2 = alternative_sum<long, int, long>(i, lng);

Another example where explicit template arguments would be useful is the ambiguous program

template <typename T> int compare(const T&, const T&);
// overloaded versions of func; each take a different function pointer type
void func(int(*) (const string&, const string&));
void func(int(*) (const int&, const int&));
func(compare<int>); // ok: explicitly specify which version of compare

We saw that the arguments to the version of compare that has a single template type parameter must match exactly. If we wanted to call the function with compatible types, such as int and short, we could use an explicit template argument to specify either int or short as the parameter type. Write a program that uses the version of compare that has one template parameter. Call compare using an explicit template argument that will let you pass arguments of type int and short.

short sval = 123;
int ival = 2;
compare(static_cast<int>(sval), ival);
compare(static_cast<short>(ival), sval);
compare<int>(sval, ival);
compare<short>(sval, ival);

c++ primer笔记8: template

1.Template Parameter List

A template parameter can be a type parameter, which represents a type, or a nontype parameter , which represents a constant expression. A nontype parameter is declared following a type specifier. A type parameter is defined following the keyword class or typename.

The only meaning we can ascribe to a template parameter is to distinguish whether the parameter is a type parameter or a nontype parameter. If it is a type parameter, then we know that the parameter represents an as yet unknown type. If it is a nontype parameter, we know it is an as yet unknown value.

2.inline Function Templates

A function template can be declared inline in the same way as a nontemplate function. The specifier is placed following the template parameter list and before the return type. It is not placed in front of the template keyword.

// ok: inline specifier follows template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of inline specifier
inline template <typename T> T min(const T&, const T&);

3.Using a Function Template

template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}

When we called compare on two string s, we passed two string objects, which we initialized from string literals
What would happen if we wrote: compare ("hi", "world").该代码会出现编译错误。因为引用的缘故,根据”hi”可将模板形参推断为char[3],根据”world”可将模板形参推断为char[6],T被推断为不同的类型。、

4.Designating Types inside the Template Definition

In addition to defining data or function members, a class may define type members. When we want to use such types inside a
function template, we must tell the compiler that the name we are using refers to a type. We must be explicit because the compiler (and a reader of our program) cannot tell by inspection when a name defined by a type parameter is a type or a value. As an example, consider the following function:

template <class Parm, class U>
Parm fcn(Parm* array, U value)
{
Parm: :size_type * p; // If Parm::size_type is a type, then a declaration
// If Parm::size_type is an object, then multiplication
}

We know that size_type must be a member of the type bound to Parm , but we do not know whether size_type is the name of a type or a data member. By default, the compiler assumes that such names name data members, not types. If we want the compiler to treat size_type as a type, then we must explicitly tell the compiler to do so:

template <class Parm, class U>
Parm fcn(Parm* array, U value)
{
typename Parm::size_type * p; // ok: declares p to be a pointer
}

5.Question

Write a function template that takes a pair of values that represent iterators of unknown type. Find the value that occurs most frequently in the sequence.

template<typename T>
typename T::value_type find_most(T begin, T end)
{

T most_iter;
size_t max_num, num;
map<typename T::value_type, size_t> container;

max_num = 0;
while(begin != end)
{
num = ++container[*begin];
if(num > max_num)
{
max_num = num;
most_iter = begin;
}

++begin;
}

return *most_iter;
}

6. Nontype Template Parameters

The function itself takes a single parameter, which is a reference to an array

template <class T, size_t N> void array_init(T (&parm)[N])
{
for (size_t i = 0; i != N; ++i) {
parm[i] = 0;
}
}
int x[42];
double y[10];
array_init(x); // instantiates array_init(int(&)[42])
array_init(y); // instantiates array_init(double(&)[10])

A template nontype parameter is a constant value inside the template definition. A nontype parameter can be used when constant expressions are required.

// Another example: Write a function template that can determine the size of an array.
template<typename T, size_t N>
size_t size(T (&array)[N])
{
return N;
}

7. Writing Generic Programs

The operations performed inside a function template constrains the types that can be used to instantiate the function. It is up to the programmer to guarantee that the types used as the function arguments actually support any operations that are used, and that
those operations behave correctly in the context in which the template uses them.

When writing template code, it is useful to keep the number of requirements placed on the argument types as small as possible.Simple though it is, our compare function illustrates two important principles for writing generic code:

  • The parameters to the template are const references.
  • The tests in the body use only < comparisons.

By making the parameters const references, we allow types that do not allow copying. Moreover, if compare is called with large objects, then this design will also make the function run faster. we reduce the requirements on types that can be used with our compare function. Those types must support < , but they need not also support > .

c++ primer笔记8: template

1.Template Parameter List

A template parameter can be a type parameter, which represents a type, or a nontype parameter , which represents a constant expression. A nontype parameter is declared following a type specifier. A type parameter is defined following the keyword class or typename.

The only meaning we can ascribe to a template parameter is to distinguish whether the parameter is a type parameter or a nontype parameter. If it is a type parameter, then we know that the parameter represents an as yet unknown type. If it is a nontype parameter, we know it is an as yet unknown value.

2.inline Function Templates

A function template can be declared inline in the same way as a nontemplate function. The specifier is placed following the template parameter list and before the return type. It is not placed in front of the template keyword.

// ok: inline specifier follows template parameter list
template <typename T> inline T min(const T&, const T&);
// error: incorrect placement of inline specifier
inline template <typename T> T min(const T&, const T&);

3.Using a Function Template

template <typename T>
int compare(const T &v1, const T &v2)
{
if (v1 < v2) return -1;
if (v2 < v1) return 1;
return 0;
}

When we called compare on two string s, we passed two string objects, which we initialized from string literals
What would happen if we wrote: compare ("hi", "world").该代码会出现编译错误。因为引用的缘故,根据”hi”可将模板形参推断为char[3],根据”world”可将模板形参推断为char[6],T被推断为不同的类型。、

4.Designating Types inside the Template Definition

In addition to defining data or function members, a class may define type members. When we want to use such types inside a
function template, we must tell the compiler that the name we are using refers to a type. We must be explicit because the compiler (and a reader of our program) cannot tell by inspection when a name defined by a type parameter is a type or a value. As an example, consider the following function:

template <class Parm, class U>
Parm fcn(Parm* array, U value)
{
Parm: :size_type * p; // If Parm::size_type is a type, then a declaration
// If Parm::size_type is an object, then multiplication
}

We know that size_type must be a member of the type bound to Parm , but we do not know whether size_type is the name of a type or a data member. By default, the compiler assumes that such names name data members, not types. If we want the compiler to treat size_type as a type, then we must explicitly tell the compiler to do so:

template <class Parm, class U>
Parm fcn(Parm* array, U value)
{
typename Parm::size_type * p; // ok: declares p to be a pointer
}

5.Question

Write a function template that takes a pair of values that represent iterators of unknown type. Find the value that occurs most frequently in the sequence.

template<typename T>
typename T::value_type find_most(T begin, T end)
{

T most_iter;
size_t max_num, num;
map<typename T::value_type, size_t> container;

max_num = 0;
while(begin != end)
{
num = ++container[*begin];
if(num > max_num)
{
max_num = num;
most_iter = begin;
}

++begin;
}

return *most_iter;
}

6. Nontype Template Parameters

The function itself takes a single parameter, which is a reference to an array

template <class T, size_t N> void array_init(T (&parm)[N])
{
for (size_t i = 0; i != N; ++i) {
parm[i] = 0;
}
}
int x[42];
double y[10];
array_init(x); // instantiates array_init(int(&)[42])
array_init(y); // instantiates array_init(double(&)[10])

A template nontype parameter is a constant value inside the template definition. A nontype parameter can be used when constant expressions are required.

// Another example: Write a function template that can determine the size of an array.
template<typename T, size_t N>
size_t size(T (&array)[N])
{
return N;
}

7. Writing Generic Programs

The operations performed inside a function template constrains the types that can be used to instantiate the function. It is up to the programmer to guarantee that the types used as the function arguments actually support any operations that are used, and that
those operations behave correctly in the context in which the template uses them.

When writing template code, it is useful to keep the number of requirements placed on the argument types as small as possible.Simple though it is, our compare function illustrates two important principles for writing generic code:

  • The parameters to the template are const references.
  • The tests in the body use only < comparisons.

By making the parameters const references, we allow types that do not allow copying. Moreover, if compare is called with large objects, then this design will also make the function run faster. we reduce the requirements on types that can be used with our compare function. Those types must support < , but they need not also support > .

c++ primer笔记7

1. Containers and Inheritance

We’d like to use containers (or built-in arrays) to hold objects that are related by inheritance. However, the fact that objects are not polymorphic affects how we can use containers with types in an inheritance hierarchy. Because derived objects are “sliced down” when assigned to a base object, containers and types related by inheritance do not mix well.

The only viable alternative would be to use the container to hold pointers to our objects. This strategy works, but at the cost of pushing onto our users the problem of managing the objects and pointers. The user must ensure that the objects pointed to stay around for as long as the container. If the objects are dynamically allocated, then the user must ensure that they are properly freed when the container goes away.

2. Handle Classes and Inheritance

One of the ironies of object-oriented programming in C++ is that we cannot use objects to support polymorphism. Instead, we must use pointers and references, not objects. Unfortunately, using pointers or references puts a burden on the users of our classes.

A common technique in C++ is to define a so-called handle class . The handle class stores and manages a pointer to the base class. The type of the object to which that pointer points will vary; it can point at either a base- or a derived-type object. Users access the operations of the inheritance hierarchy through the handle. Because the handle uses its pointer to execute those operations, the behavior of virtual members will vary at run time depending on the kind of object to which the handle is actually bound. Users of the handle thus obtain dynamic behavior but do not themselves have to worry about managing the pointer.

3. A Pointerlike Handle

We’ll define a pointerlike handle class, named Sales_item , to represent our Item_base hierarchy. Users of Sales_item will use it as if it were a pointer: Users will bind a Sales_item to an object of type Item_base and will then use the * and -> operations to execute Item_base operations:

// bind a handle to a Bulk_item object
Sales_item item(Bulk_item("0-201-82470-1", 35, 3, .20));
item->net_price(); // virtual call to net_price function

However, users won’t have to manage the object to which the handle points; the Sales_item class will do that part of the job. When users call a function through a Sales_item , they’ll get polymorphic behavior.

The use-counted classes we’ve used so far have used a companion class to store the pointer and associated use count. In this class, we’ll use a different design. The Sales_item class will have two data members, both of which are pointers: One pointer will point to the Item_base object and the other will point to the use count. The Item_base pointer might point to an Item_base object or an object of a type derived from Item_base.

class Sales_item {
public:
// default constructor: unbound handle
Sales_item(): p(0), use(new std::size_t(1)) { }
// attaches a handle to a copy of the Item_base object
Sales_item(const Item_base&);
// copy control members to manage the use count and pointers
Sales_item(const Sales_item &i):
p(i.p), use(i.use) { ++*use; }
~Sales_item() { decr_use(); }
Sales_item& operator=(const Sales_item&);
// member access operators
const Item_base *operator->() const { if (p) return p;
else throw std::logic_error("unbound Sales_item"); }
const Item_base &operator*() const { if (p) return *p;
else throw std::logic_error("unbound Sales_item"); }
private:
Item_base *p; // pointer to shared item
std::size_t *use; // pointer to shared use count
// called by both destructor and assignment operator to free pointers
void decr_use()
{ if (--*use == 0) { delete p; delete use; } }

};

Sales_item&
Sales_item::operator=(const Sales_item &rhs)
{
++*rhs.use;
decr_use();
p = rhs.p;
use = rhs.use;
return *this;
}

Sales_item(const Item_base&);The constructor will allocate a new object of the appropriate type and copy the parameter into that newly allocated object. That way the Sales_item class will own the object and can guarantee that the object is not deleted until the last Sales_item attached to the object goes away. To implement the constructor that takes an Item_base , we must first solve a problem: We do not know the actual type of the object that the constructor is given. We know that it is an Item_base or an object of a type derived from Item_base. The common approach to solving this problem is to define a virtual operation to do the copy, which we’ll name clone.

class Item_base {
public:
virtual Item_base* clone() const
{ return new Item_base(*this); }
};
class Bulk_item : public Item_base {
public:
Bulk_item* clone() const
{ return new Bulk_item(*this); }
};

Sales_item::Sales_item(const Item_base &item):
p(item.clone()), use(new std::size_t(1)) { }

If the base instance of a virtual function returns a reference or pointer to a class type, the derived version of the virtual may return a class publicly derived from the class returned by the base class instance (or a pointer or a reference to a class type).

c++ primer笔记6

1. Using a Derived Object to Initialize or Assign a Base Object

Because there is a conversion from reference to derived to reference to base, these copy-control members can be used to initialize or assign a base object from a derived object:

Item_base item; // object of base type
Bulk_item bulk; // object of derived type
// ok: uses Item_base::Item_base(const Item_base&) constructor
Item_base item(bulk); // bulk is "sliced down" to its Item_base portion
// ok: calls Item_base::operator=(const Item_base&)
item = bulk; // bulk is "sliced down" to its Item_base portion

When we call the Item_base copy constructor or assignment operator on an object of type Bulk_item , the following steps happen:

  • The Bulk_item object is converted to a reference to Item_base, which means only that an Item_base reference is bound to the Bulk_item object.
  • That reference is passed as an argument to the copy constructor or assignment operator.
  • Those operators use the Item_base part of Bulk_item to initialize and assign, respectively, the members of the Item_base on which the constructor or assignment was called.
  • Once the operator completes, the object is an Item_base . It contains a copy of the Item_base part of the Bulk_item from which it was initialized or assigned, but the Bulk_item parts of the argument are ignored.

2. Defining a Default Constructor

Item_base(const std::string &book = "", double sales_price = 0.0): 
isbn(book), price(sales_price) { }

class Bulk_item : public Item_base {
public:
Bulk_item(): min_qty(0), discount(0.0) { }
// as before
};

This constructor uses the constructor initializer list to initialize its min_qty and discount members. The constructor initializer also implicitly invokes the Item_base default constructor to initialize its base-class part. The effect of running this constructor is that first the Item_base part is initialized using the Item_base default constructor. That constructor sets isbn to the empty string and price to zero. After the Item_base constructor finishes, the members of the Bulk_item part are initialized, and the (empty) body of the constructor is executed.
The constructor initializer list supplies initial values for a class’ base class and members. It does not specify the order in which those initializations are done. The base class is initialized first and then the members of the derived class are initialized in the order in which they are declared.
A class may initialize only its own immediate base class. An immediate base class is the class named in the derivation list.

3. Defining a Derived Copy Constructor

If a derived class explicitly defines its own copy constructor or assignment operator, that definition completely overrides the
defaults. The copy constructor and assignment operator for inherited classes are responsible for copying or assigning their base-
class components as well as the members in the class itself.

class Base { /* ... */ };
class Derived: public Base {
public:
// Base::Base(const Base&) not invoked automatically
Derived(const Derived& d):
Base(d) /* other member initialization */ { /*... */ }
};

The initializer Base(d) converts the derived object, d , to a reference to its base part and invokes the base-class copy constructor. Had the initializer for the base class been omitted,

Derived(const Derived& d) /* derived member initizations */
{/* ... */ }

the effect would be to run the Base default constructor to initialize the base part of the object. Assuming that the initialization of the Derived members copied the corresponding elements from d , then the newly constructed object would be oddly configured: Its Base part would hold default values, while its Derived members would be copies of another object.

4. Derived-Class Assignment Operator

If the derived class defines its own assignment operator, then that operator must assign the base part explicitly:

// Base::operator=(const Base&) not invoked automatically
Derived &Derived::operator=(const Derived &rhs)
{
if (this != &rhs) {
Base::operator=(rhs); // assigns the base part
// do whatever needed to clean up the old value in the derived part
// assign the members from the derived
}
return *this;
}

5. Derived-Class Destructor

The destructor works differently from the copy constructor and assignment operator: The derived destructor is never responsible for destroying the members of its base objects. The compiler always implicitly invokes the destructor for the base part of a derived object. Objects are destroyed in the opposite order from which they are constructed: The derived destructor is run first, and then the base-class destructors are invoked, walking back up the inheritance hierarchy.
The root class of an inheritance hierarchy should define a virtual destructor even if the destructor has no work to do.

6. Virtuals in Constructors and Destructors

A derived object is constructed by first running a base-class constructor to initialize the base part of the object. While the base-class constructor is executing, the derived part of the object is uninitialized. In effect, the object is not yet a derived object.

When a derived object is destroyed, its derived part is destroyed first, and then its base parts are destroyed in the reverse order of how they were constructed.

In both cases, while a constructor or destructor is running, the object is incomplete. To accommodate this incompleteness, the compiler treats the object as if its type changes during construction or destruction. Inside a base-class constructor or destructor, a derived object is treated as if it were an object of the base type.

If a virtual is called from inside a constructor or destructor, then the version that is run is the one defined for the type of the constructor or destructor itself.

7. Name Collisions and Inheritance

A derived-class member with the same name as a member of the base class hides direct access to the base-class member. We can access a hidden base-class member by using the scope operator. The derived-class member hides the base-class member within the scope of the derived class. The base member is hidden, even if the prototypes of the functions differ :

struct Base {
int memfcn();
};
struct Derived : Base {
int memfcn(int); // hides memfcn in the base
};
Derived d; Base b;
b.memfcn(); // calls Base::memfcn
d.memfcn(10); // calls Derived::memfcn
d.memfcn(); // error: memfcn with no arguments is hidden
d.Base::memfcn(); // ok: calls Base::memfcn

8. Name Lookup and Inheritance

Understanding how function calls are resolved is crucial to understanding inheritance hierarchies in C++. The following four steps are followed:

  • Start by determining the static type of the object, reference, or pointer through which the function is called.
  • Look for the function in that class. If it is not found, look in the immediate base class and continue up the chain of classes until either the function is found or the last class is searched. If the name is not found in the class or its enclosing base classes, then the call is in error.
  • Once the name is found, do normal type-checking to see if this call is legal given the definition that was found.
  • Assuming the call is legal, the compiler generates code. If the function is virtual and the call is through a reference or pointer, then the compiler generates code to determine which version to run based on the dynamic type of the object. Otherwise, the compiler generates code to call the function directly.

c++ primer笔记5

1. protected Members

protected has important property: A derived object may access the protected members of its base class only through a derived
object. The derived class has no special access to the protected members of base type objects. 这里说的情况是在Bulk_item的成员函数里面。如果是在user code代码里面,protected属性的成员肯定是不能访问的。

void Bulk_item::memfcn(const Bulk_item &d, const Item_base &b)
{
// attempt to use protected member
double ret = price; // ok: uses this->price
ret = d.price; // ok: uses price from a Bulk_item object
ret = b.price; // error: no access to price from an Item_base
}

2. Derived Classes and virtual Functions

If a derived class does not redefine a virtual, then the version it uses is the one defined in its base class. A derived type must include a declaration for each inherited member it intends to redefine.
With one exception, the declaration of a virtual function in the derived class must exactly match the way the function is defined in the base. That exception applies to virtuals that return a reference (or pointer) to a type that is itself a base class. A virtual function in a derived class can return a reference (or pointer) to a class that is publicly derived from the type returned by the base-class function.

3. Overriding the Virtual Mechanism

In some cases, we want to override the virtual mechanism and force a call to use a particular version of a virtual function. We can do so by using the scope operator:

Item_base *baseP = &derived;
// calls version from the base class regardless of the dynamic type of baseP
double d = baseP->Item_base::net_price(42);

This code forces the call to net_price to be resolved to the version defined in Item_base . The call will be resolved at compile time. When a derived virtual calls the base-class version, it must do so explicitly using the scope operator. If the derived function neglected to do so, then the call would be resolved at run time and would be a call to itself, resulting in an infinite recursion.

4. 无题

A publicly derived class inherits the interface of its base class; it has the same interface as its base class. In well-designed class hierarchies, objects of a publicly derived class can be used wherever an object of the base class is expected. By far the most common form of inheritance is public.

5. restore the access level of an inherited member

class Base {
public:
std::size_t size() const { return n; }
protected:
std::size_t n;
};
class Derived : private Base { . . . };

In this hierarchy, size is public in Base but private in Derived. To make size public in Derived we can add a using declaration for it to a public section in Derived. By changing the definition of Derived as follows, we can make the size member accessible to users and n accessible to classes subsequently derived from Derived:

class Derived : private Base {
public:
// maintain access levels for members related to the size of the object
using Base::size;
protected:
using Base::n;
// ...
};

The derived class can restore the access level of an inherited member. The access level cannot be made more or less restrictive than the level originally specified within the base class.

6. Default Inheritance Protection Levels

A derived class defined using the class keyword has private inheritance. A class is defined with the struct keyword, has public inheritance.The only differences between class and struct are the default protection level for members and the default protection level for a derivation.

class Base { /* ... */ };
struct D1 : Base { /* ... */ }; // public inheritance by default
class D2 : Base { /* ... */ }; // private inheritance by default

修改linux系统下的最大文件描述符限制

linux下最大文件描述符的限制有两个方面,一个是进程级的限制,另外一个则是系统级限制。

1. 系统级的限制

/proc/sys/fs/file-max是系统中的所有进程总共可以同时打开的文件数量,可以通过修改内核参数来更改该限制:sysctl -w fs.file-max=102400。但是,使用sysctl命令更改是临时的,如果想永久更改需要在/etc/sysctl.conf添加fs.file-max=102400,保存退出后使用sysctl -p 命令使其生效。

/proc/sys/fs/file-max:This file defines a system-wide limit on the number of open files for all processes. (See also setrlimit(2), which can be used by a process to set the per-process limit, RLIMIT_NOFILE, on the number of files it may open.) If you get lots of error messages about running out of file handles, try increasing this value: echo 100000 > /proc/sys/fs/file-max. The kernel constant NR_OPEN imposes an upper limit on the value that may be placed in file-max. If you increase /proc/sys/fs/file-max, be sure to increase /proc/sys/fs/inode-max to 3-4 times the new value of /proc/sys/fs/file-max, or you will run out of inodes.

2. 进程级的限制

修改一个进程能够同时打开的文件数量限制有两种方式:(1)通过bash的内置命令ulimit (2)在程序中调用setrlimit函数

(1)ulimit is a bash built_in command. It provides control over the resources available to the shell and to processes started by it.
执行ulimit -n命令可以看到本次登录的shell会话的文件描述符的限制,一般情况下是1024。当然可以通过ulimit -SHn 102400 命令来修改该限制。
(2) The getrlimit() and setrlimit() system calls get and set resource limits respectively. Each resource has an associated soft and hard limit, as defined by the rlimit structure:

struct rlimit {
rlim_t rlim_cur; /* Soft limit */
rlim_t rlim_max; /* Hard limit (ceiling for rlim_cur) */
};

The soft limit is the value that the kernel enforces for the corresponding resource. The hard limit acts as a ceiling for the soft limit: an unprivileged process may only set its soft limit to a value in the range from 0 up to the hard limit, and (irreversibly) lower its hard limit. A privileged process may make arbitrary changes to either limit value. The value RLIM_INFINITY denotes no limit on a resource.

不管通过ulimit还是setrlimit来修改限制,都只能对当前进程及其子进程有效。而且它们都会受到/etc/security/limits.conf文件中的数量限制。如下所示,

vi /etc/security/limits.conf
xiaohua hard nofile 100
xiaohua soft nofile 100

则用户xiaohua的每个进程能够同时打开的文件数量的上限是100,通过ulimit和setrlimit的修改的上限值不能超过它。而且可以看出,其实/etc/security/limits.conf是用户级的限制,能够限制某个用户或者用户组的每个进程能够同时打开的文件数量的上限。修改该文件,保存退出后重新登录,里面设置的值才会生效。

linux点滴(1)

1. 查看linux内核版本

方法一:
        命令: uname -a // uname is short for unix name
        作用: 查看系统内核版本号及系统名称
方法二:
        命令: cat /proc/version
        作用: 查看目录”/proc”下version的信息,也可以得到当前系统的内核版本号及系统名称

补充说明:/proc文件系统,它不是普通的文件系统,而是系统内核的映像。The proc file system is a pseudo-file system which is used as an interface to kernel data structures. It is commonly mounted at /proc. Most of it is read-only, but some files allow kernel variables to be changed. 我们使用命令“uname -a”的信息就是从该文件获取的。

2. 查看进程打开的所有文件描述符

方法一:ll /proc/<进程PID>/fd/
方法二:lsof -p <进程PID>

3. 设置和查看系统资源限制ulimit

ulimit(short for user limits): it provides control over the resources available to the shell and to processes started by it.

Syntax
ulimit [-acdfHlmnpsStuv] [limit]

Options

-S Change and report the soft limit associated with a resource.
-H Change and report the hard limit associated with a resource.

-a All current limits are reported.
-c The maximum size of core files created.
-d The maximum size of a process's data segment.
-f The maximum size of files created by the shell(default option)
-l The maximum size that can be locked into memory.
-m The maximum resident set size.
-n The maximum number of open file descriptors.
-p The pipe buffer size.
-s The maximum stack size.
-t The maximum amount of cpu time in seconds.
-u The maximum number of processes available to a single user.
-v The maximum amount of virtual memory available to the process.

ulimit 作为对资源使用限制的一种工作,是有其作用范围的。那么,它限制的对象是单个用户,单个进程,还是整个系统呢?事实上,ulimit 限制的是当前shell进程以及其派生的子进程。举例来说,如果用户同时运行了两个shell终端进程,只在其中一个环境中执行了ulimit –f 100,则该shell进程里创建文件的大小受到相应的限制,而同时另一个shell终端包括其上运行的子程序都不会受其影响。

那么,是否有针对某个具体用户的资源加以限制的方法呢?答案是有的,方法是通过修改系统的 /etc/security/limits 配置文件。该文件不仅能限制指定用户的资源使用,还能限制指定组的资源使用。该文件的每一行都是对限定的一个描述,格式如下:
<domain> <type> <item> <value>
domain 表示用户或者组的名字,还可以使用 * 作为通配符。type 可以有两个值,soft 和 hard。item 则表示需要限定的资源,可以有很多候选值,如 stack,cpu,nofile 等等,分别表示最大的堆栈大小,占用的 cpu 时间,以及打开的文件数。通过添加对应的一行描述,则可以产生相应的限制。例如:
* hard nofile 100
该行配置语句限定了任意用户所能创建的最大文件数是100。

现在已经可以对进程和用户分别做资源限制了,看似已经足够了,其实不然。很多应用需要对整个系统的资源使用做一个总的限制,这时候我们需要修改 /proc 下的配置文件。/proc 目录下包含了很多系统当前状态的参数,例如 /proc/sys/kernel/pid_max,/proc/sys/net/ipv4/ip_local_port_range 等等,从文件的名字大致可以猜出所限制的资源种类。由于该目录下涉及的文件众多,在此不一一介绍。

4. error while loading shared libraries

库文件在链接(静态库和共享库)和运行(仅限于使用共享库的程序,因为静态库已经链接到可执行程序中了)时被使用,其搜索路径是在系统中进行设置的。一般linux系统把 /lib 和 /usr/lib 两个目录作为默认的库搜索路径,所以使用这两个目录中的库时不需要进行设置搜索路径即可直接使用。对于处于默认库搜索路径之外的库,需要将库的位置添加到 库的搜索路径之中。设置库文件的搜索路径有下列两种方式,可任选其一使用:

(1) 修改环境变量LD_LIBRARY_PATH
在环境变量 LD_LIBRARY_PATH 中指明库的搜索路径。例如$export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH。并且LD_LIBRARY_PATH路径优先于系统默认路径之前查找
(2) 修改配置文件ld.so.conf
添加方法也极其简单,将库文件的绝对路径直接写进去就OK了,一行一个。例如:
/usr/X11R6/lib
/usr/local/lib
需要注意的是:第二种搜索路径的设置方式对于程序链接时的库(包括共享库和静态库)的定位已经足够了,但是对于使用了共享库的程序的执行还是不够的。这是因为为了加快程序执行时对共享库的定位速度,避免使用搜索路径查找共享库的低效率,所以是直接读取库列表文件 /etc/ld.so.cache 从中进行搜索的。/etc/ld.so.cache 是一个非文本的数据文件,不能直接编辑,它是根据 /etc/ld.so.conf 中设置的搜索路径由 /sbin/ldconfig 命令将这些搜索路径下的共享库文件集中在一起而生成的(ldconfig 命令要以 root 权限执行)。

因此,为了保证程序执行时对库的定位,在 /etc/ld.so.conf 中进行了库搜索路径的设置之后,还必须要运行 /sbin/ldconfig 命令更新 /etc/ld.so.cache 文件之后才可以。ldconfig ,简单的说,它的作用就是将/etc/ld.so.conf列出的路径下的库文件缓存到/etc/ld.so.cache 以供使用。因此当安装完一些库文件,(例如刚安装好glib),或者修改ld.so.conf增加新的库路径后,需要运行一下 /sbin/ldconfig使所有的库文件都被缓存到ld.so.cache中,如果没做,即使库文件明明就在/usr/lib下的,也是不会被使用的,结果编译过程中报错,缺少xxx库,去查看发现明明就在那放着。

在程序链接时,对于库文件(静态库和共享库)的搜索路径,除了上面的设置方式之外,还可以通过 -L 参数显式指定。因为用 -L 设置的路径将被优先搜索,所以在链接的时候通常都会以这种方式直接指定要链接的库的路径。

现代链接器在处理动态库时将 链接时路径(Link-time path)和运行时路径(Run-time path)分开,用户可以通过-L指定链接时库的路径,通过-R(或-rpath)指定程序运行时库的路径,大大提高了库应用的灵活性和搜索库文件的效率。

5. 墙上时钟时间,用户cpu时间,系统cpu时间

时钟时间(墙上时钟时间wall clock time):从进程从开始运行到结束,时钟走过的时间,这其中包含了进程在阻塞和等待状态的时间。
用户CPU时间:用户进程获得了CPU资源以后,在用户态执行的时间。
系统CPU时间:用户进程获得了CPU资源以后,在内核态的执行时间。

进程的三种状态为阻塞、就绪、运行。

时钟时间 = 阻塞时间 + 就绪时间 + 运行时间
用户CPU时间 = 运行状态下用户空间的时间
系统CPU时间 = 运行状态下内核空间的时间

用户CPU时间 + 系统CPU时间 = 运行时间。

select

We should set nonblocking mode on all network handles, and use select() or poll() to tell which network handle has data waiting. With this scheme, the kernel tells you whether a file descriptor is ready. It’s particularly important to remember that readiness notification from the kernel is only a hint; the file descriptor might not be ready anymore when you try to read from it. That’s why it’s important to use nonblocking mode when using readiness notification.

select的基本用法不再叙述。select uses descriptor sets, typically an array of integers, with each bit in each integer corresponding to a descriptor. For example, using 32-bit integers, the first element of the array corresponds to descriptors 0 through 31, the second element of the array corresponds to descriptors 32 through 63, and so on. All the implementation details are irrelevant to the application and are hidden in the fd_set datatype and the following four macros:

void FD_ZERO(fd_set *fdset);          /* clear all bits in fdset */

void FD_SET(int fd, fd_set *fdset); /* turn on the bit for fd in fdset */

void FD_CLR(int fd, fd_set *fdset); /* turn off the bit for fd in fdset */

int FD_ISSET(int fd, fd_set *fdset); /* is the bit for fd on in fdset ? */

We allocate a descriptor set of the fd_set datatype, we set and test the bits in the set using these macros, and we can also assign it to another descriptor set across an equals sign (=) in C. The constant FD_SETSIZE, defined by including , is the number of descriptors in the fd_set datatype. Its value is often 1024.

也就是说,使用select有一个限制,那就是它支持的文件描述符的数量,一般是1024。Many implementations have declarations similar to the following, which are taken from the 4.4BSD header:

#ifndef FD_SETSIZE
#define FD_SETSIZE 256
#endif

This makes us think that we can just #define FD_SETSIZE to some larger value before including this header to increase the size of the descriptor sets used by select. Unfortunately, this normally does not work. To see what is wrong, notice that Figure 16.53 of TCPv2 declares three descriptor sets within the kernel and also uses the kernel’s definition of FD_SETSIZE as the upper limit. The only way to increase the size of the descriptor sets is to increase the value of FD_SETSIZE and then recompile the kernel. Changing the value without recompiling the kernel is inadequate.
但是自己在实验的时候,发现输出1024,这是为什么呢?难道编译的是否内核里的宏定义直接覆盖用户提供的。

#include <stdio.h>
#define FD_SETSIZE 512
#include <sys/types.h>

int main()
{

printf("%d\n", FD_SETSIZE);
}

IO

1、水平触发 VS 边缘触发

水平触发(level-triggered,也被称为条件触发)LT: 只要满足条件,就触发一个事件(只要有数据没有被获取,内核就不断通知你)

边缘触发(edge-triggered)ET: 每当状态变化时,触发一个事件(仅当状态由not ready 到 ready时触发一个事件)
Eedge-triggered readiness notification means you give the kernel a file descriptor, and later, when that descriptor transitions from not ready to ready, the kernel notifies you somehow. It then assumes you know the file descriptor is ready, and will not send any more readiness notifications of that type for that file descriptor until you do something that causes the file descriptor to no longer be ready (e.g. until you receive the EWOULDBLOCK error on a send, recv, or accept call, or a send or recv transfers less than the requested number of bytes). epoll is the recommended edge-triggered poll replacement for the 2.6 Linux kernel.

举个读socket的例子,假定经过长时间的沉默后,现在来了100个字节,这时无论边缘触发和条件触发都会产生一个read ready notification通知应用程序可读。应用程序读了50个字节,然后重新调用api等待io事件。这时水平触发的api会因为还有50个字节可读从 而立即返回用户一个read ready notification。而边缘触发的api会因为可读这个状态没有发生变化而陷入长期等待。 因此在使用边缘触发的api时,要注意每次都要读到socket返回EWOULDBLOCK为止,否则这个socket就算废了。而使用条件触发的api 时,如果应用程序不需要写就不要关注socket可写的事件,否则就会无限次的立即返回一个write ready notification。大家常用的select就是属于水平触发这一类,长期关注socket写事件会出现CPU 100%的毛病。

非阻塞调用和EINTR

As is known, some blocking calls like read and write would return -1 and set errno to EINTR, and we need handle this. My question is: Does this apply for non-blocking calls, e.g, set socket to O_NONBLOCK?

答案是:对于非阻塞调用,它们不会返回EINTR错误。 If you take a look at the man pages of various systems for the following socket functions bind(), connect(), send(), and receive(), or look those up in the POSIX standard, you’ll notice something interesting: All these functions except one may return -1 and set errno to EINTR. The one function that is not documented to ever fail with EINTR is bind(). And bind() is also the only function of that list that will never block by default. So it seems that only blocking functions may fail because of EINTR, including read() and write(), yet if these functions never block, they also will never fail with EINTR and if you use O_NONBLOCK, those functions will never block.

Of course, even with non-blocking I/O, the read call may have temporarily interrupted by a signal but why would the system have to indicate that? Every function call, whether this is a system function or one written by the user, may be temporarily interrupted by a signal, really every single one, no exception. If the system would have to inform the user whenever that happens, all system functions could possibly fail because of EINTR. However, even if there was a signal interruption, the functions usually perform their task all the way to the end, that’s why this interruption is irrelevant. The error EINTR is used to tell the caller that the action he has requested was not performed because of a signal interruption, but in case of non-blocking I/O, there is no reason why the function should not perform the read or the write request, unless it cannot be performed right now, but then this can be indicated by an appropriate error(EAGAIN or EWOULDBLOCK ).

To confirm my theory, I took a look at the kernel of MacOS (10.8), which is still largely based on the FreeBSD kernel and it seems to confirm the suspicion. If a read call is currently not possible, as no data are available, the kernel checks for the O_NONBLOCK flag in the file descriptor flags. If this flag is set, it fails immediately with EAGAIN. If it is not set, it puts the current thread to sleep by calling a function named msleep(). This function causes the current thread to sleep until it is explicitly woken up (which is the case if data becomes ready for reading) or a timeout has been hit (e.g. you can set a receive timeout on sockets). Yet the thread is also woken up, if a signal is delivered, in which case msleep() itself returns EINTR and the next higher layer just passes this error through. So it is msleep() that produces the EINTR error, but if the O_NONBLOCK flag is set, msleep() is never called in the first place, hence this error cannot be returned.

TCP系列之SIGPIPE

When a process writes to a socket that has received an RST, the SIGPIPE signal is sent to the process. 这是UNIX网络编程卷一(5.13章节)中的内容。但是在实际的编程中发现,当套接字收到RST报文段后,第一次read或者write会产生ECONNRESET错误,第二次read或者write时才会触发SIGPIPE信号。这与UNIX网络编程卷一中的描述“当套接字接收到RST后,我们再执行write操作则会触发SIGPIPE信号”不一样。

The default action of this signal is to terminate the process, so the process must catch the signal to avoid being involuntarily terminated. If the process either catches the signal and returns from the signal handler, or ignores the signal, the write operation returns EPIPE. If special actions are needed when the signal occurs (writing to a log file perhaps), then the signal should be caught and any desired actions can be performed in the signal handler.

TCP系列之connect

当客户端进程(例如浏览器)需要同时发起多个连接的时候,如果使用阻塞connect,那么先发起第一个连接并等待它返回,然后第二个,第三……这样效率不高,在等待连接返回的时间内,可以做一些其他的任务处理。而且,如果某个连接因为超时才返回的话,那么客户端进程阻塞在这个连接上的时间将会更多。所以,这种情况下,我们更喜欢非阻塞connect。

在使用非阻塞connect的时候,有以下几点需要注意:
1、When a TCP socket is set to nonblocking and then connect is called, connect returns immediately with an error of EINPROGRESS but the TCP three-way handshake continues.
2、Even though the socket is nonblocking, if the server to which we are connecting is on the same host, the connection is normally established immediately when we call connect.
3、POSIX have the following two rules regarding select and nonblocking connects:(1) When the connection completes successfully, the descriptor becomes writable. (2) When the connection establishment encounters an error, the descriptor becomes both readable and writable.

// 省略 select

FD_ISSET(sockfd, &wset)
{
int error = 0;
socklen_t error_len = sizeof(error);

int ret = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &error_len);

if(ret < 0)
{
printf("connection error: %s", strerror(errno));
return -1;
}
else
{
if(error != 0)
{
errno = error;
printf("connection error: %s", strerror(errno));
return -1;
}
else
{
printf("connection success");
return 0;
}
}
}

There are portability problems with various socket implementations and nonblocking connects. If an error occurred, Berkeley-derived implementations of getsockopt return 0 with the pending error returned in our variable error. But Solaris causes getsockopt itself to return –1 with errno set to the pending error.

对于一个阻塞的套接字,当我们调用connect时被信号中断了,它会返回EINTR错误。但是要注意,我们不能对这个套接字再次调用connect,否则将返回EADDRINUSE错误。此时正确的做法是调用select,就像使用非阻塞connect一样。select returns when the connection completes successfully (making the socket writable) or when the connection fails (making the socket readable and writable).

TCP系列之连接建立


这是正常的TCP连接的三次握手过程。但是,为什么需要三次握手呢?如果只有二次握手又会有什么问题呢?

1、The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.这主要是为了防止已经失效的连接请求报文段突然又传送到了B,从而产生错误。具体看下面的场景:A发出连接请求报文段,但是该连接请求报文段因网络原因暂时滞留在某网络结点。A因为没有收到确认,于是重传一次连接请求报文段,这个重传的连接请求报文段被B接收到了,并且A也收到了确认,于是连接建立成功。它们传输完数据之后,释放连接。但是,此时第一个连接请求报文段奇迹般地又到达了B。本来这是一个早已失效的报文段,但是B误认为是A又发出的一个新的连接请求。于是向A发送确认报文段,同意建立连接。如果不采用三次握手,对B而言,当它发出确认报文段后,新的连接就建立了,但这并不是一个真实的连接,造成资源浪费。

2、如果从更深层次的角度去考虑,我们看看TCP的三次握手到底做了些什么事情。在TCP中,通讯双方都会使用序列号(sequence number)来记录自己已经发送了哪些数据。而且,接收方也会使用对方的序列号对自己已经收到的数据进行确认。从安全角度考虑,序列号不会从0开始,而是一个随机值。因为TCP是双向通讯的,双方都可以发送数据,所以双方都必须产生一个随机数来作为自己的初始序列号。当然,通讯双方也必须把自己的初始序列号告知对方,这样双方才能正常通讯。Alice和Bob之间的TCP通话是这样开始的:
Alice —-> Bob   // SYNchronize with my Initial Sequence Number of X
Alice <—- Bob   // I received your syn, I ACKnowledge that I am ready for [X+1]
Alice <—- Bob   // SYNchronize with my Initial Sequence Number of Y
Alice —-> Bob   // I received your syn, I ACKnowledge that I am ready for [Y+1]
Alice将自己的初始序列号告知Bob,Bob对其进行确认;Bob将自己的初始序列号告知Alice,Alice对其进行确认。实际上,为了提高效率,第2步和第3步是在同一个报文段中完成的。
Alice —-> Bob   // SYN
Alice <—- Bob   // SYN ACK
Alice —-> Bob   // ACK
所以,如果只有两次握手的话,所实现的只是Alice将自己的初始序列号告知了Bob,Bob也对其进行了确认。即只能Alice对Bob发送数据,但是Bob却不能对Alice发送数据。

3、下面的英文解释跟2是同样道理的。
To establish a connection, the three-way (or 3-step) handshake occurs:
(1) SYN: The active open is performed by the client sending a SYN to the server. The client sets the segment’s sequence number to a random value A.
(2) SYN-ACK: In response, the server replies with a SYN-ACK. The acknowledgment number is set to one more than the received sequence number i.e. A+1, and the sequence number that the server chooses for the packet is another random number, B.
(3) ACK: Finally, the client sends an ACK back to the server. The sequence number is set to the received acknowledgement value i.e. A+1, and the acknowledgement number is set to one more than the received sequence number i.e. B+1.

At this point, both the client and server have received an acknowledgment of the connection. The steps 1, 2 establish the connection parameter (sequence number) for one direction and it is acknowledged. The steps 2, 3 establish the connection parameter (sequence number) for the other direction and it is acknowledged. With these, a full-duplex communication is established.

4、两次握手更容易遭受DoS攻击

Sender           Receiver
SYN
------------------------> (Connection established at Receiver)

SYN + ACK
<------------------------ (Connection established at Sender)

The above sequence of steps shows what should happen ideally. Now, how easy it is to make DoS attack. Simply, send a SYN packet to the Receiver and it would open a connection at Receiver. Sender need not even care about (SYN + ACK) packet coming back from Receiver.

TCP系列之连接关闭


        这是正常的TCP释放连接过程。其中,最不容易让人理解的地方就是时间等待(TIME-WAIT)状态。从上图可以看到,主动关闭连接的一方会经历时间等待状态,并且TIME-WAIT状态要持续2MSL(最长报文段寿命,Maximum Segment Life),之后才能进入CLOSED状态。那为什么主动关闭连接的一方要处于TIME-WAIT状态并等待2MSL时间呢?

1、为了保证A发送的最后一个ACK报文段能够到达B。这个ACK报文段有可能丢失,因而是处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认。B会超时重传这个FIN+ACK报文段,那么A就能在2MSL时间内收到这个重传的FIN+ACK报文段。接着A重传一次确认,重新启动2MSL计时器。最后A和B都正常进入到CLOSED状态。

2、为了保证已经失效的重复报文段(old duplicate segments)都从网络中消失。A在发送完最后一个ACK报文段后,再经过2MSL,就可以使在本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接(源IP、源端口、目的IP、目的端口完全一样)中不会出现这种旧的报文段。
        To understand the second reason for the TIME_WAIT state, assume we have a TCP connection between 12.106.32.254 port 1500 and 206.168.112.219 port 21. This connection is closed and then sometime later, we establish another connection between the same IP addresses and ports: 12.106.32.254 port 1500 and 206.168.112.219 port 21. This latter connection is called an incarnation of the previous connection since the IP addresses and ports are the same. TCP must prevent old duplicates from a connection from reappearing at some later time and being misinterpreted as belonging to a new incarnation of the same connection. To do this, TCP will not initiate a new incarnation of a connection that is currently in the TIME_WAIT state. By enforcing this rule, we are guaranteed that when we successfully establish a TCP connection, all old duplicates from previous incarnations of the connection have expired in the network.

TCP系列之accept

0x01 描述

上图表示的是已经建立好TCP连接之后,在服务端还没有调用accept之前接收到了RST报文段的异常情况。但是,对这种异常终止的连接的处理是依赖于不同的实现的。源自Berkeley的实现完全在内核里处理这种异常终止的连接,内核不会将这个连接传递给服务器进程。然而,大多数的SVR4的实现在服务器进程调用accept的时候返回一个错误值,该错误值依赖于具体的实现。这些SVR4实现返回一个EPROTO (protocol error)错误,但是POSIX规定返回ECONNABORTED(“software caused connection abort”)错误。

0x02 与select结合的问题

我们知道,当有一个新的连接建立的时候,select会把监听套接字描述符标识为可读,然后我们可以调用accept去获得这个连接。那现在看下面的场景:
1、客户端与服务端建立连接
2、服务器进程的select标识监听套接字可读,但是它因为处理其他事情,并没有立即调用accept
3、客户端终止连接,并发送RST报文段
4、此时服务器进程调用accept,这时会有什么结果呢?如果监听套接字描述符是阻塞的,那么在源自Berkeley的实现上,该accept将会阻塞,直到有新的连接到来,在其他的实现上则会返回一个错误。如果监听套接字描述符是非阻塞的,那么在源自Berkeley的实现上,会返回EWOULDBLOCK错误,在其他的实现上则会返回ECONNABORTED或者EPROTO错误。

所以,针对这种情况的解决方案是:
1、将监听套接字描述符始终设置为非阻塞的。
2、当accpet返回-1时,检查errno的值是否是:EWOULDBLOCK(源自Berkeley的实现)、ECONNABORTED(POSIX实现)、EPROTO(SVR4实现)

一个小插曲:EAGAIN or EWOULDBLOCK, The socket is marked nonblocking and no connections are present to be accepted. POSIX.1-2001 and POSIX.1-2008 allow either error to be returned for this case, and do not require these constants to have the same value, so a portable application should check for both possibilities.

0x03 SO_LINGER Socket Option

This option specifies how the close function operates for a connection-oriented protocol. By default, close returns immediately, but if there is any data still remaining in the socket send buffer, the system will try to deliver the data to the peer. The SO_LINGER socket option lets us change this default. This option requires the following structure to be passed between the user process and the kernel.

struct linger {
int l_onoff; /* 0=off, nonzero=on */
int l_linger; /* linger time, POSIX specifies units as seconds */
};

Calling setsockopt leads to one of the following three scenarios:
1、If l_onoff is 0, the option is turned off. The value of l_linger is ignored and the previously discussed TCP default applies: close returns immediately.
2、If l_onoff is nonzero and l_linger is zero, TCP aborts the connection when it is closed. That is, TCP discards any data still remaining in the socket send buffer and sends an RST to the peer, not the normal four-packet connection termination sequence. This avoids TCP’s TIME_WAIT state, but in doing so, leaves open the possibility of another incarnation of this connection being created within 2MSL seconds and having old duplicate segments from the just-terminated connection being incorrectly delivered to the new incarnation.
3、If l_onoff is nonzero and l_linger is nonzero, then the kernel will linger when the socket is closed. That is, if there is any data still remaining in the socket send buffer, the process is put to sleep until either: (i) all the data is sent and acknowledged by the peer TCP, or (ii) the linger time expires. If the socket has been set to nonblocking, it will not wait for the close to complete, even if the linger time is nonzero. When using this feature of the SO_LINGER option, it is important for the application to check the return value from close, because if the linger time expires before the remaining data is sent and acknowledged, close returns EWOULDBLOCK and any remaining data in the send buffer is discarded.

当我们调用close,并从其返回之后,到底意味着什么呢?The basic principle here is that a successful return from close, with the SO_LINGER socket option set, only tells us that the data we sent (and our FIN) have been acknowledged by the peer TCP. This does not tell us whether the peer application has read the data. If we do not set the SO_LINGER socket option, we do not know whether the peer TCP has acknowledged the data.

One way for the client to know that the server has read its data is to call shutdown (with a second argument of SHUT_WR) instead of close and wait for the peer to close its end of the connection.

0x04 发送RST报文段

struct linger ling;
ling.l_onoff = 1; /* cause RST to be sent on close() */
ling.l_linger = 0;
setsockopt(sockfd, SOL\_SOCKET, SO\_LINGER, &ling, sizeof(ling));
close(sockfd);

c++ primer笔记4

1. Conversion Operators

A conversion operator is a special kind of class member function. It defines a conversion that converts a value of a class type to a value of some other type. A conversion operator is declared in the class body by specifying the keyword operator followed by the type that is the target type of the conversion.

class SmallInt 
{
public:
SmallInt(int i = 0): val(i)
{
if (i < 0 || i > 255)
throw std::out_of_range("Bad SmallInt initializer");
}
operator int() const { return val; }
private:
std::size_t val;
};

A conversion function takes the general form operator type().
A conversion function must be a member function. The function may not specify a return type, and the parameter list must be empty.
A class-type conversion may not be followed by another class-type conversion. If more than one class-type conversion is needed then the code is in error. 例如,A可以转换成B,B可以转换成C,那么在需要C的地方提供一个A是错误的。

2. Explicit Conversion Cast

int a = 10;
SmallInt si(18);
int sum1 = si + a; // ok
int sum2 = static_cast<int>(si) + a; // ok, instruct compiler to cast si to int

3. Ambiguities When Two Classes Define Conversions

class Integral;
class SmallInt {
public:
SmallInt(Integral &); // convert from Integral to SmallInt
// ...
};
class Integral {
public:
operator SmallInt() const; // convert from SmallInt to Integral
// ...
};
void compute(SmallInt);
Integral int_val;
compute(int_val); // error: ambiguous

In this case, we cannot use a cast to resolve the ambiguity, the cast itself could use either the conversion operation or the constructor. Instead, we would need to explicitly call the conversion operator or the constructor:

compute(int_val.operator SmallInt()); // ok: use conversion operator
compute(SmallInt(int_val)); // ok: use SmallInt constructor

The best way to avoid ambiguities or surprises is to avoid writing pairs of classes where each offers an implicit conversion to the other.

4. Multiple Conversions and Overload Resolution

class SmallInt {
public:
// Conversions to int or double from SmallInt
// Usually it is unwise to define conversions to multiple arithmetic types
operator int() const { return val; }
operator double() const { return val; }
// ...
private:
std::size_t val;
};
void compute(int);
void compute(double);
void compute(long double);
SmallInt si;
compute(si); // error: ambiguous

In this case we could use the operator int to convert si and call the version of compute that takes an int. Or we could use operator double to convert si and call compute(double). The solutions is

compute(static_cast<int>(si)); // ok: convert and call compute(int)

Let’s look at overload resolution when multiple conversion constructors exist:

class SmallInt {
public:
SmallInt(int = 0);
};
class Integral {
public:
Integral(int = 0);
};
void manip(const Integral&);
void manip(const SmallInt&);
manip(10); // error: ambiguous

The caller can disambiguate by explicitly constructing a value of the desired type:

manip(SmallInt(10)); // ok: call manip(SmallInt)
manip(Integral(10)); // ok: call manip(Integral)

Needing to use a constructor or a cast to convert an argument in a call to an overloaded function is a sign of bad design.

5. 标准转换优于类类型转换

class LongDouble 
{

public:
LongDouble(double);
// ...
};

void calc(int);
void calc(LongDouble);
double dval;
calc(dval); // which function?

调用void calc(int)所需要的转换:将实参dval由double类型转换为int
调用void calc(LongDouble)需要的转换:将实参dval由double类型转换为LongDouble
因为标准转换优于类类型转换,所以void calc(int)为最佳可行函数

6. Caution: Conversions and Operators

Correctly designing the overloaded operators, conversion constructors, and conversion functions for a class requires some care. In particular, ambiguities are easy to generate if a class defines both conversion operators and overloaded operators. A few rules of thumb can be helpful:

  1. Never define mutually converting classes, that is, if class Foo has a constructor that takes an object of class Bar, do not give class Bar a conversion operator to type Foo.
  2. Avoid conversions to the built-in arithmetic types. In particular, if you do define a conversion to an arithmetic type, then
    (1) Do not define overloaded versions of the operators that take arithmetic types. If users need to use these operators, the
    conversion operation will convert objects of your type, and then the built-in operators can be used.
    (2)Do not define a conversion to more than one arithmetic type. Let the standard conversions provide conversions to the other
    arithmetic types.

The easiest rule of all: Avoid defining conversion functions and limit nonexplicit constructors to those that are “obviously right.”

c++ primer笔记3

1、数组元素和容器元素的初始化

vector<string> svec(5);

The compiler initializes svec by first using the default string constructor to create a temporary value. The copy constructor is then used to copy the temporary into each element of svec.

string strs[5] = {string("ff"), string("22")};

第一、二个元素用拷贝构造函数初始化,其余的用默认构造函数初始化

2、禁止复制

By declaring (but not defining) a private copy constructor, we can forestall any attempt to copy an object of the class type: Attempts to make copies in user code will be flagged as an error at compile time, and attempts to make copies in member functions or friends will result in an error at link time.

3、复制构造函数、赋值运算符、析构函数

A useful rule of thumb is that if a class needs a destructor, it will also need the assignment operator and a copy constructor.
This rule is often referred to as the Rule of Three.
An important difference between the destructor and the copy constructor or assignment operator is that even if we write our own destructor, the synthesized destructor is still run.
Best Practice:The assignment operator often does the same work as is needed in the copy constructor and destructor. In such cases, the common work should be put in private utility functions.

4.运算符重载

An overloaded operator must have at least one operand of class or enumeration type. This rule enforces the requirement that an overloaded operator may not redefine the meaning of the operators when applied to objects of built-in type.
Overloaded operators make no guarantees about the order in which operands are evaluated. In particular, the operand-evaluation guarantees of the built-in logical AND, logical OR, and comma operators are not preserved. Both operands to an overloaded version of && or || are always evaluated. The order in which those operands are evaluated is not stipulated. The order in which the operands to the comma are evaluated is also not defined. For this reason, it is usually a bad idea to overload &&, || , or the comma operator.

5.Overloading the Output Operator <<

To be consistent with the IO library, the operator should take an ostream& as its first parameter and a reference to a const object of the class type as its second. The operator should return a reference to its ostream parameter.

ostream& operator <<(ostream& os, const ClassType &object)
{
os << // ...
return os;
}

The ostream is non const because writing to the stream changes its state. The parameter is a reference because we cannot copy an ostream object.

6. Overloading the Input Operator >>

A more important, and less obvious, difference between input and output operators is that input operators must deal with the
possibility of errors and end-of-file.

istream& operator>>(istream& in, Sales_item& s)
{
double price;
in >> s.isbn >> s.units_sold >> price;
// check that the inputs succeeded
if (in)
s.revenue = s.units_sold * price;
else
s = Sales_item(); // input failed: reset object to default state
return in;
}

Our Sales_item input operator reads the expected values and checks whether an error occurred. The kinds of errors that might happen include:

  1. Any of the read operations could fail because an incorrect value was provided. For example, after reading isbn , the input operator assumes that the next two items will be numeric data. If nonnumeric data is input, that read and any subsequent use of the stream will fail.
  2. Any of the reads could hit end-of-file or some other error on the input stream.

Rather than checking each read, we check once before using the data we read. If an input operator detects that the input failed, it is often a good idea to make sure that the object is in a usable and consistent state. Doing so is particularly important if the object might have been partially written before the error occurred.
In addition to handling any errors that might occur, an input operator might need to set the condition state of its input istream parameter.Our input operator is quite simple, the only errors we care about are those that could happen during the reads. If the reads succeed, then our input operator is correct and has no need to do additional checking.
Some input operators do need to do additional checking. For example, our input operator might check that the isbn we read is in an appropriate format. We might have read data successfully, but these data might not be suitable when interpreted as an ISBN. In such cases, the input operator might need to set the condition state to indicate failure, even though technically speaking the actual IO was successful. Usually an input operator needs to set only the failbit. Setting eofbit would imply that the file was exhausted, and setting badbit would indicate that the stream was corrupted. These errors are best left to the IO library itself to indicate.

7. Subscript Operator

One complication in defining the subscript operator is that we want it to do the right thing when used as either the left- or right-hand operand of an assignment. To appear on the left-handside, it must yield an lvalue, which we can achieve by specifying the return type as a reference. As long as subscript returns a reference, it can be used on either side of an assignment.
It is also a good idea to be able to subscript const and non const objects. When applied to a const object, the return should be a const reference so that it is not usable as the target of an assignment.
Ordinarily, a class that defines subscript needs to define two versions: one that is a non const member and returns a reference and one that is a const member and returns a const reference.

class Foo 
{
public:
int &operator[] (const size_t);
const int &operator[] (const size_t) const;
// other interface members
private:
vector<int> data;
// other member data and private utility functions
};
int& Foo::operator[] (const size_t index)
{
return data[index]; // no range checking on index
}
const int& Foo::operator[] (const size_t index) const
{
return data[index]; // no range checking on index
}

8. Member Access Operators

To support pointerlike classes, such as iterators, the language allows the dereference ( * ) and arrow ( ->) operators to be overloaded. The dereference and arrow operators are often used in classes that implement smart pointers.

// private class for use by ScreenPtr only
class ScrPtr
{

friend class ScreenPtr;
Screen *sp;
size_t use;
ScrPtr(Screen *p): sp(p), use(1) { }

~ScrPtr() { delete sp; }
};

class ScreenPtr
{

public:
// no default constructor: ScreenPtrs must be bound to an object
ScreenPtr(Screen *p): ptr(new ScrPtr(p)) { }
// copy members and increment the use count
ScreenPtr(const ScreenPtr &orig):
ptr(orig.ptr) { ++ptr->use; }
ScreenPtr& operator=(const ScreenPtr& rhs)
{
++rhs.ptr->use; // increment use count on rhs first
if (--ptr->use == 0)
delete ptr; // if use count goes to 0 on this object, delete it
ptr = rhs.ptr;
return *this;
}
~ScreenPtr() { if (--ptr->use == 0) delete ptr; }
private:
ScrPtr *ptr;
};

Among the fundamental operations a pointer supports are dereference and arrow. We can give our class these operations as follows:

class ScreenPtr 
{
public:
// constructor and copy control members as before
Screen &operator*() { return *ptr->sp; }
Screen *operator->() { return ptr->sp; }
const Screen &operator*() const { return *ptr->sp; }
const Screen *operator->() const { return ptr->sp; }
private:
ScrPtr *ptr;
};

Operator arrow is unusual. It may appear to be a binary operator that takes an object and a member name, dereferencing the object in order to fetch the member. Despite appearances, the arrow operator takes no explicit parameter.
There is no second parameter because the right-hand operand of -> is not an expression. Rather, the right-hand operand is an identifier that corresponds to a member of a class. There is no obvious, useful way to pass an identifier as a parameter to a function. Instead, the compiler handles the work of fetching the member. The overloaded arrow operator must return either a pointer to a class type or an object of a class type that defines its own operator arrow. When we write point->action(), the compiler evaluates this code as follows:

  1. If point is a pointer to a class object that has a member named action , then the compiler writes code to call the action member of that object.
  2. Otherwise, if point is an object of a class that defines operator-> , then point->action is the same as point.operator->()->action. That is, we execute operator->() on point and then repeat these three steps, using the result of executing operator-> on point.
  3. Otherwise, the code is in error.

    9. Arithmetic and Relational Operators

    最佳实践:将+、-、<、>等操作符定义为非成员函数,因为它们不改变对象的状态;将+=等定义为成员函数,因为它们改变了对象的状态。
    Sales_item  operator+(const Sales_item& lhs, const Sales_item& rhs)
    {
    Sales_item ret(lhs); // copy lhs into a local object that we'll return
    ret += rhs; // add in the contents of rhs
    return ret; // return ret by value
    }

注意,+重载操作符调用了+=重载操作符。同样的,==和!=也应该只实现一套逻辑,让其中一个去调用另外一个。

10. Call Operator and Function Objects

class GT_cls {
public:
GT_cls(size_t val = 0): bound(val) { }
bool operator()(const string &s)
{ return s.size() >= bound; }

private:
std::string::size_type bound;
};

count_if(words.begin(), words.end(), GT_cls(6));

The standard library defines a set of arithmetic, relational, and logical function-object classes. The library also defines a set of function adaptors that allow us to specialize or extend the function-object classes defined by the library or those that we define ourselves. The library function-object types are defined in the functional header.
using a library function object with the algorithms:

sort(svec.begin(), svec.end(), greater<string>());

The standard library provides a set of function adaptors with which to specialize and extend both unary and binary function objects. The function adaptors are divided into the following two categories.

  1. Binders: A binder is a function adaptor that converts a binary function object into a unary function object by binding one of the operands to a given value.
  2. Negators: A negator is a function adaptor that reverses the truth value of a predicate function object.

The library defines two binder adaptors: bind1st and bind2nd . Each binder takes a function object and a value. As you might expect, bind1st binds the given value to the first argument of the binary function object, and bind2nd binds the value to the second. For example, to count all the elements within a container that are less than or equal to 10 , we would pass count_if the
following:

count_if(vec.begin(), vec.end(), bind2nd(less_equal<int>(), 10));

The third argument to count_if uses the bind2nd function adaptor. That adaptor returns a function object that applies the <= operator using 10 as the right-hand operand. This call to count_if counts the number of elements in the input range that are less than or equal to 10.
The library also provides two negators: not1 and not2 . Again, as you might expect, not1 reverses the truth value of a unary predicate function object, and not2 reverses the truth value of a binary predicate function object.
To negate our binding of the less_equal function object, we would write

count_if(vec.begin(), vec.end(), not1(bind2nd(less_equal<int>(), 10)));

Here we first bind the second operand of the less_equal object to 10 , effectively transforming that binary operation into a unary operation. We then negate the return from the operation using not1 . The effect is that each element will be tested to see if it is <= to 10. Then, the truth value of that result will be negated. In effect, this call counts those elements that are not <= to 10.

11. Defining the Increment/Decrement Operators

class CheckedPtr {
public:
CheckedPtr& operator++(); // prefix operators
CheckedPtr& operator--();
CheckedPtr operator++(int); // postfix operators
CheckedPtr operator--(int);
};

c++ primer笔记2

1、构造函数初始化列表

Conceptually, we can think of a constructor as executing in two phases: (1) the initialization phase and (2) a general computation phase. The computation phase consists of all the statements within the body of the constructor. Data members of class type are always initialized in the initialization phase, regardless of whether the member is initialized explicitly in the constructor initializer list. Initialization happens before the computation phase begins.

class A
{
public:
// 调用string的复制构造函数
A(const string &str) : data(str)

// 第一阶段调用string的默认构造函数,第二阶段调用string的赋值操作符
A(const string &str)
{

data = str;
}


private:

string data;
};

Some members must be initialized in the constructor initializer. Members of a class type that do not have a default constructor and members that are const or reference types must be initialized in the constructor initializer regardless of type.
The order in which members are initialized is the order in which the members are defined, not the order in which the initializer list provides.

2、隐式类型转换函数

A constructor that can be called with a single argument defines an implicit conversion from the parameter type to the class type.Ordinarily, single-parameter constructors should be explicit unless there is an obvious reason to want to define an implicit conversion. Making constructors explicit may avoid mistakes, and a user can explicitly construct an object when a conversion is useful.

3、友元的声明和范围

class Screen 
{
// Window_Mgrmust be defined before class Screen
friend Window_Mgr& Window_Mgr::relocate(int, int, Screen&);
};

Interdependencies among friend declarations and the definitions of the friends can require some care in order to structure the classes correctly. In the previous example, class Window_Mgr must have been defined. Otherwise, class Screen could not name a Window_Mgr function as a friend. However, the relocate function itself can’t be defined until class Screen has been defined. After all, it was made a friend in order to access the members of class Screen.
A friend declaration introduces the named class or nonmember function into the surrounding scope. Moreover, a friend function
may be defined inside the class. The scope of the function is exported to the scope enclosing the class definition.

class X 
{

friend class Y;
friend void f() { /* ok to define friend function in the class body */ }
};
class Z
{

Y *ymem; // ok: declaration for class Y introduced by friend in X
void g() { return ::f(); } // ok: declaration of f introduced by X
};

4、静态成员

Because a static member is not part of any object, static member functions may not be declared as const . After all, declaring a member function as const is a promise not to modify the object of which the function is a member. Finally, static member functions may also not be declared as virtual.
static data members must be defined (exactly once) outside the class body.

class Account 
{
public:
void applyint() { amount += amount * interestRate; }
static double rate() { return interestRate; }
static void rate(double); // sets a new rate
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
static void test();
};

double Account::interestRate = initRate();
void Account::test()
{
initRate();
}

Like other member definitions, the definition of a static member is in class scope once the member name is seen. 静态数据成员interestRate的定义和静态函数test的定义都是在class类作用域内,所以它们都可以使用类作用域内的成员,哪怕它们是private。As a result, we can use the static member function named initRate directly without qualification as the initializer for interestRate. Note that even though initRate is private, we can use this function to initialize interestRate. The definition of interestRate, like any other member definition, is in the scope of the class and hence has access to the private members of the class. The static keyword, however, is used only on the declaration inside the class body. Definitions are not labeled static.

class Account 
{
public:
static double rate() { return interestRate; }
static void rate(double); // sets a new rate
private:
static const int period = 30; // interest posted every 30 days
double daily_tbl[period]; // ok: period is constant expression
};

A const static data member of integral type can be initialized within the class body.When a const static data member is initialized in the class body, the data member must still be defined outside the class definition.
const int Account::period;

5、静态成员不是类对象的一部分

Because static data members are not part of any object, they can be used in ways that would be illegal for non static data members.
As an example, the type of a static data member can be the class type of which it is a member. A non static data member is restricted to being declared as a pointer or a reference to an object of its class:

class Bar 
{

public:
// ...
private:
static Bar mem1; // ok
Bar *mem2; // ok
Bar mem3; // error
};

a static data member can be used as a default argument:

class Screen 
{
public:
Screen& clear(char = bkground);
private:
static const char bkground = '#';
};

c++ primer笔记1

1. 基本数据类型

容器定义的类型别名  
size_type Unsigned integral type large enough to hold size of largest possible container of this container type
iterator Type of the iterator for this container type
const_iterator Type of the iterator that can read but not write the elements
reverse_iterator Iterator that addresses elements in reverse order
const_reverse_iterator Reverse iterator that can read but not write the elements
difference_type Signed integral type large enough to hold the difference, which might be negative, between two iterators
value_type Element type
reference Element’s lvalue type; synonym for value_type&
const_reference Element’s const lvalue type; same as const value_type&
Types Defined by the map Class  
map::key_type The type of the keys used to index the map.
map::mapped_type The type of the values associated with the keys in the map.
map::value_type A pair whose first element has type const map::key_type and second has type map::mapped_type
iterator的类型成员  
value_type 该迭代器实际指向的数据的类型T
pointer T *
reference T&

2、对迭代器解引用返回的是元素的引用

if (!ilist.empty()) 
{
// val and val2 refer to the same element
list<int>::reference val = *ilist.begin();
list<int>::reference val2 = ilist.front();

// last and last2 refer to the same element
list<int>::reference last = *--ilist.end();
list<int>::reference last2 = ilist.back();
}

3、map的下标操作

map <string, int> word_count;
word_count["Anna"] = 1;
  1. word_count is searched for the element whose key is Anna . The element is not found.
  2. A new keyvalue pair is inserted into word_count . The key is a const string holding Anna .
    The value is value initialized, meaning in this case that the value is 0.
  3. The new keyvalue pair is inserted into word_count .
  4. The newly inserted element is fetched and is given the value 1.

For map , if the key is not already present, a new element is created and inserted into the map for that key.

4、Algorithms Never Execute Container Operations

The generic algorithms do not themselves execute container operations. They operate solely in terms of iterators and iterator operations. The fact that the algorithms operate in terms of iterators and not container operations has a perhaps surprising but essential implication: When used on “ordinary” iterators, algorithms never change the size of the underlying container. As we’ll see, algorithms may change the values of the elements stored in the container, and they may move elements around within the
container. They do not, however, ever add or remove elements directly.
There is a special class of iterator, the inserters, that do more than traverse the sequence to which they are bound. When we assign to these iterators, they execute insert operations on the underlying container. When an algorithm operates on one of these
iterators, the iterator may have the effect of adding elements to the container. The algorithm itself, however, never does so.

5、类的前向声明

An incomplete type can be used only in limited ways. Objects of the type may not be defined. An incomplete type may be used to define only pointers or references to the type or to declare (but not define) functions that use the type as a paremeter or return type.

6、Returning *this from a const Member Function

In an ordinary non const member function, the type of this is a const pointer to the class type. We may change the value to which this points but cannot change the address that this holds. In a const member function, the type of this is a const pointer to a const class-type object. We may change neither the object to which this points nor the address that this holds.
We cannot return a plain reference to the class object from a const member function. A const member function may return *this only as a const reference.

class Screen
{

public:
const Screen& print() const
{
cout << "print" << endl;
return *this;
}
};

7、Overloading Based on const

We can overload a member function based on whether it is const for the same reasons that we can overload a function based on whether a pointer parameter points to const. A const object will use only the const member. A non const object could use
either member, but the non const version is a better match.

class Screen 
{
public:
Screen& display(std::ostream &os)
{ do_display(os); return *this; }

const Screen& display(std::ostream &os) const
{ do_display(os); return *this; }

private:
void do_display(std::ostream &os) const
{ os << contents; }

};

8、Parameter Lists and Function Bodies Are in Class Scope

In a member function defined outside the class, the parameter list and member-function body both appear after the member name. These are defined inside the class scope and so may refer to other class members without qualification.

9、Function Return Types Aren’t Always in Class Scope

In contrast to the parameter types, the return type appears before the member name. If the function is defined outside the class body, then the name used for the return type is outside the class scope. If the return type uses a type defined by the class, it must use the fully qualified name.

10、类成员声明中的名字查找(Name Lookup for Class Member Declarations)

1、The declarations of the class members that appear before the use of the name are considered.
2、If the lookup in step 1 is not successful, the declarations that appear in the scope in which the class is defined, and that appear before the class definition itself, are considered.

11、类成员定义中的名字查找(Name Lookup in Class Member Definitions)

1、Declarations in the member-function local scopes are considered first.
2、If the a declaration for the name is not found in the member function, the declarations for all the class members are considered.
3、If a declaration for the name is not found in the class, the declarations that appear in scope before the member function definition are considered.

int height;
class Screen
{
public:
void dummy_fcn(int height)
{
cursor = width * height;
}
private:
int cursor;
int height, width;
};

dummy_fun函数内赋值语句中的height指的是dummy_fun函数参数中的height。如果想使用Screen中的成员height,则cursor = width * Screen::height; 如果想使用全局变量height,则cursor = width * ::height;

12、typedef 引起的微妙错误

typedef string Type;
Type initVal();
class Exercise
{

public:
typedef double Type;
Type setVal(Type); // Type为double
Type initVal(); // Type为double
private:
int val;
};

// 注意,在类的外部定义函数时,返回值的类型
Exercise::Type Exercise::setVal(Type parm)
{
val = parm + initVal();
}

如果把typedef double Type放到setVal和initVal的后面,就会出现错误。因为按照类成员声明的名字查找规则,setVal中的Type为string。等到编译器解析到typedef double Type语句时,Type有两种意思了,于是发生编译错误。

error: declaration of ‘typedef double Exercise::Type’
error: changes meaning of ‘Type’ from ‘typedef struct std::string Type’

wpa_supplicant

```

// 启动wpa_supplicant时候的命令行参数保存在struct wpa_params结构的变量中
struct wpa_params
{
int daemonize;
int wait_for_monitor;
char *pid_file;
int wpa_debug_level;
int wpa_debug_show_keys;
int wpa_debug_timestamp;

/* Global ctrl_iface path/parameter */
char *ctrl_interface;

/* Global ctrl_iface group */
char *ctrl_interface_group;


int dbus_ctrl_interface;
const char *wpa_debug_file_path;
int wpa_debug_syslog;
int wpa_debug_tracing;

/**
 * override_driver - Optional driver parameter override
 *
 * This parameter can be used to override the driver parameter in
 * dynamic interface addition to force a specific driver wrapper to be
 * used instead.
 */
char *override_driver;

/**
 * override_ctrl_interface - Optional ctrl_interface override
 *
 * This parameter can be used to override the ctrl_interface parameter
 * in dynamic interface addition to force a control interface to be
 * created.
 */
char *override_ctrl_interface;

char *entropy_file;

ifdef CONFIG_P2P

/**
 * conf_p2p_dev - Configuration file used to hold the
 * P2P Device configuration parameters.
 *
 * This can also be %NULL. In such a case, if a P2P Device dedicated
 * interfaces is created, the main configuration file will be used.
 */
char *conf_p2p_dev;

endif / CONFIG_P2P /

};

wpa_supplicant

最简单的运行wpa_supplicant的命令

wpa\_supplicant -iwlan0 -C/var/run/wpa_supplicant -d

-iwlan0表示使用wlan0无线网卡
-C表示控制接口(ctrl interface)所在的目录。控制接口提供了外部程序与wpa_supplicant进行交互的接口,外部程序通过控制接口来获取wpa_supplicant的相关信息,并且能够控制它的行为。
-d表示输出调试信息
程序成功执行后,会在/var/run/wpa_supplicant目录下面生成wlan0的套接字文件
srwxrwx--- 1 root root 0 Dec 15 10:42 wlan0

如果系统中有多个无线网卡,可以开启多个wpa_supplicant进程,每个wpa_supplicant进程控制一个无线网卡。当然,也可以只使用一个wpa_supplicant进程来控制多个无线网卡,每个无线网卡通过-N命令行参数来分隔:

wpa\_supplicant -iwlan0 -C/var/run/wpa_supplicant -N
-iwlan1 -C/var/run/wpa_supplicant -d

此时,会生成两个控制接口

ll /var/run/wpa_supplicant

srwxrwx--- 1 root root 0 Dec 15 10:58 wlan0
srwxrwx--- 1 root root 0 Dec 15 10:58 wlan1

wpa_cli程序的核心是一个事件循环eloop,在事件循环eloop中会循环处理信号事件、定时器事件、文件描述符的读写事件。而这些事件信息都存储在struct eloop_data这样的结构变量中,程序循环处理事件也就是循环处理struct eloop_data结构变量。

1. 信号事件

与信号事件有关的数据结构是struct eloop_signal,具体信息如下:

struct eloop_signal 
{
int sig; /* signal number */
void *user_data;
eloop_signal_handler handler; /* signal handler */
int signaled; /* 进程是否收到该信号的标志 */
};

struct eloop_data中与信号事件有关的数据域如下:

struct eloop_data 
{
/* other fields */

int signal_count; /* 程序初始化过程中注册的信号处理函数的数量 */
struct eloop_signal *signals; /* 程序初始化过程中注册的所有的信号处理函数 */
int signaled; /* 程序运行过程中是否有未处理的信号的标志 */
}

wpa_cli的信号处理过程是这样的:无论进程收到什么信号,都会调用eloop_handle_signal函数来处理信号。在eloop_handle_signal内,只会简单的在eloop_data中记录某个信号发生了,除此之外,没有做任何其他事情。而真正的信号处理过程是推迟到事件循环eloop中去处理的。wpa_cli在初始化的时候仅仅注册了SIGINT和SIGTERM这两个信号的处理函数。

2. 定时器事件

与定时器事件有关的数据结构是struct eloop_timeout,具体信息如下:

struct eloop_timeout 
{
struct dl_list list; // 定时器事件的链表
struct os_reltime time; // 事件的发生时间
void *eloop_data;
void *user_data;
eloop_timeout_handler handler; // 定时器事件的处理函数
};

struct eloop_data中与信号事件有关的数据域如下:

struct eloop_data 
{

/* other fields */

struct dl_list timeout; // 定时器事件的链表头
}

在交互(Interactive)模式下,wpa_cli在进入事件循环函数eloop_run之前,注册了try_connection定时器事件。try_connection定时器事件在eloop_run中被执行,它会调用wpa_cli_open_connection去连接wpa_supplicant程序创建的控制接口,如果连接失败,则把自己再注册为一个1秒后触发的定时器事件;如果连接成功,则会生成两个与wpa_supplicant的连接:struct wpa_ctrl *ctrl_conn和struct wpa_ctrl *mon_conn。与wpa_supplicant进行的命令请求和响应,是通过ctrl_conn这个连接来进行的,但是与它有关的读写事件并不会注册在eloop_data中;而与mon_conn有关的读事件是注册到eloop_data中的,用来读取wpa_supplicant发送的广播事件。连接成功之后,wpa_cli会注册一个每隔一段时间就与wpa_supplicant通讯的心跳事件,还会把标准输入注册到eloop_data中,以便能够读取用户输入的命令。
与wpa_supplicant通讯的心跳事件的处理函数wpa_cli_ping到底做了哪些事情呢?其实,它只是调用了wpa_ctrl_request。那wpa_ctrl_request又是何许人也呢?它是wpa_cli用来向wpa_supplicant发送命令请求并接收响应数据的通用接口。在wpa_ctrl_request中,程序利用struct wpa_ctrl *ctrl_conn这个连接向wpa_supplicant发送命令请求,然后读取wpa_supplicant的数据响应并打印到标准输出,这与前文中的“与ctrl_conn有关的读写事件并不会注册在eloop_data中”是一致的。

至此,我们可以知道,eloop_data中注册了一个定时器事件(每隔一段时间就与wpa_supplicant通讯的心跳事件)和两个文件描述符读事件(一个是标准输入,用于获取用户的输入;另一个是mon_conn连接的读事件,用于获取wpa_supplicant主动发送的广播事件)。

3. 文件描述符的读写事件

与文件描述符的读写事件有关的数据结构是struct eloop_sock和struct eloop_sock_table,具体信息如下:

struct eloop_sock 
{
int sock; // 文件描述符
void *eloop_data;
void *user_data;
eloop_sock_handler handler; // 处理函数
};
struct eloop_sock_table
{
int count; // 该类型的文件描述符的数量
struct eloop_sock *table; // 该类型的文件描述符集合
eloop_event_type type; // 文件描述符类型:READ,WRITE,EXCEPTION
int changed; // 该类型的文件描述符集合是否发生过改变
};

struct eloop_data中与信号事件有关的数据域如下:

struct eloop_data 
{
/* other fields */

int max_sock;

struct eloop_sock_table readers;
struct eloop_sock_table writers;
struct eloop_sock_table exceptions;
}

通过前面的分析可以知道,eloop_data中只有在readers中注册了两个文件描述符。其中一个是mon_conn连接的读事件,当wpa_supplicant主动发送广播信息时,会调用wpa_cli_mon_receive函数将广播信息打印到标准输出。另一个是标准输入的读事件,当用户输出一个命令的时候,会在wpa_cli中所支持的命令集合wpa_cli_commands中进行匹配,

struct wpa_cli_cmd 
{
const char *cmd;
int (*handler)(struct wpa_ctrl *ctrl, int argc, char *argv[]);
char ** (*completion)(const char *str, int pos);
enum wpa_cli_cmd_flags flags;
const char *usage;
};

const struct wpa_cli_cmd wpa_cli_commands[] =
{
{ "status", wpa_cli_cmd_status, NULL,
cli_cmd_flag_none,
"[verbose] = get current WPA/EAPOL/EAP status" },

// other commands
}

如果匹配到某一条命令,则调用该命令的的处理函数handler,该handler与定时器心跳事件的处理函数wpa_cli_ping的处理过程是一样的。

svn分支和合并

Creating a Branch

Creating a branch is very simple—you make a copy of the project in the repository using the svn copy command.

$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/branches/my-calc-branch \
-m "Creating a private branch of /calc/trunk."
Committed revision 341.

Working with Your Branch

Now that you’ve created a branch of the project, you can check out a new working copy to start using it:

$ svn checkout http://svn.example.com/repos/calc/branches/my-calc-branch
A my-calc-branch/Makefile
A my-calc-branch/integer.c
A my-calc-branch/button.c
Checked out revision 341.

There’s nothing special about this working copy; it simply mirrors a different directory in the repository. Subversion has no internal concept of a branch—it knows only how to make copies. When you copy a directory, the resultant directory is only a “branch” because you attach that meaning to it. You may think of the directory differently, or treat it differently, but to Subversion it’s just an ordinary directory that happens to carry some extra historical information. Most teams follow a convention of putting all branches into a /branches directory, but you’re free to invent any policy you wish.

Changeset

A changeset is just a collection of changes with a unique name. The changes might include textual edits to file contents, modifications to tree structure, or tweaks to metadata. In more common speak, a changeset is just a patch with a name you can refer
to.
In Subversion, a global revision number N names a tree in the repository. It’s also the name of an implicit changeset: if you
compare tree N with tree N-1, you can derive the exact patch that was committed. For this reason, it’s easy to think of revision N as not just a tree, but a changeset as well. And Subversion’s svn merge command is able to use revision numbers. You can merge specific changesets from one branch to another by naming them in the merge arguments: passing -c 9238 to svn merge would merge changeset r9238 into your working copy.

Merge

A better name for the merge command might have been svn diff-and-apply, because that’s all that happens: two repository trees are compared, and the differences are applied to a working copy. If you’re using svn merge to do basic copying of changes between branches, it will generally do the right thing automatically. For example, a command such as the following:
$ svn merge http://svn.example.com/repos/calc/some-branch
will attempt to duplicate any changes made on some-branch into your current working directory. The command is smart enough to only duplicate changes that your working copy doesn’t yet have. If you repeat this command once a week, it will only duplicate the
“newest” branch changes that happened since you last merged.If you choose to use the svn merge command in all its full glory by giving it specific revision ranges to duplicate, the command takes three main arguments:

  1. An initial repository tree (often called the left side of the comparison)
  2. A final repository tree (often called the right side of the comparison)
  3. A working copy to accept the differences as local changes (often called the target of the merge)
    Once these three arguments are specified, the two trees are compared, and the differences are applied to the target working copy as local modifications. When the command is done, the results are no different than if you had hand-edited the files or run various svn add or svn delete commands yourself. If you like the results, you can commit them. If you don’t like the results, you can simply svn revert all of the changes.
    $ svn merge http://svn.example.com/repos/branch1@150 \
    http://svn.example.com/repos/branch2@212 \
    my-working-copy
    $ svn merge -r 100:200 http://svn.example.com/repos/trunk my-working-copy
    $ svn merge -r 100:200 http://svn.example.com/repos/trunk
    $ svn merge -c 355 http://svn.example.com/repos/calc/trunk

The first syntax lays out all three arguments explicitly, naming each tree in the form URL@REV and naming the working copy target. The second syntax can be used as a shorthand for situations when you’re comparing two different revisions of the same URL.
The last syntax shows how the working copy argument is optional; if omitted, it defaults to the current directory. The last syntax shows only replicate just a single change.

Keeping a Branch in Sync

Let’s suppose that a week has passed since you started working on your private branch. Your new feature isn’t finished yet, but at the same time you know that other people on your team have continued to make important changes in the project’s /trunk. In fact, this is a best practice: frequently keeping your branch in sync with the main development line helps prevent “surprise” conflicts when it comes time for you to fold your changes back into the trunk.
Subversion is aware of the history of your branch and knows when it divided away from the trunk. To replicate the latest, greatest trunk changes to your branch, first make sure your working copy of the branch is “clean”—that it has no local modifications reported by svn status. Then simply run:

$ svn merge http://svn.example.com/repos/calc/trunk
--- Merging r345 through r356 into '.':
U button.c
U integer.c

This basic syntax—svn merge URL—tells Subversion to merge all recent changes from the URL to the current working directory. After running the prior example, your branch working copy now contains new local modifications, and these edits are duplications of all of the changes that have happened on the trunk since you first created your branch:

$ svn status
M .
M button.c
M integer.c

At this point, the wise thing to do is look at the changes carefully with svn diff, and then build and test your branch. Notice that the current working directory (“.”) has also been modified; the svn diff will show that its svn:mergeinfo property has been either created or modified. This is important merge-related metadata that you should not touch, since it will be needed by future svn merge commands.
After performing the merge, you might also need to resolve some conflicts (just as you do with svn update) or possibly make some small edits to get things working properly. (Remember, just because there are no syntactic conflicts doesn’t mean there aren’t any semantic conflicts!) If you encounter serious problems, you can always abort the local changes by running svn revert . -R (which will undo all local modifications) and start a long “what’s going on?” discussion with your collaborators. If things look good, however, you can submit these changes into the repository:

$ svn commit -m "Merged latest trunk changes to my-calc-branch."
Sending .
Sending button.c
Sending integer.c
Transmitting file data ..
Committed revision 357.

At this point, your private branch is now “in sync” with the trunk, so you can rest easier knowing that as you continue to work in isolation, you’re not drifting too far away from what everyone else is doing.
Suppose that another week has passed. You’ve committed more changes to your branch, and your comrades have continued to improve the trunk as well. Once again, you’d like to replicate the latest trunk changes to your branch and bring yourself in sync. Just run the same merge command again!

$ svn merge http://svn.example.com/repos/calc/trunk
--- Merging r357 through r380 into '.':
U integer.c
U Makefile
A README

Subversion knows which trunk changes you’ve already replicated to your branch, so it carefully replicates only those changes you don’t yet have. What happens when you finally finish your work, though? The process is simple. First, bring your branch in sync with the trunk again, just as you’ve been doing all along:

$ svn merge http://svn.example.com/repos/calc/trunk
--- Merging r381 through r385 into '.':
U button.c
U README
$ # build, test, ...
$ svn commit -m "Final merge of trunk changes to my-calc-branch."
Sending .
Sending button.c
Sending README
Transmitting file data ..
Committed revision 390.

Now, you use svn merge to replicate your branch changes back into the trunk. You’ll need an up-to-date working copy of /trunk. However you get a trunk working copy, remember that it’s a best practice to do your merge into a working copy that has no local edits and has been recently updated (i.e., is not a mixture of local revisions).

$ svn update # (make sure the working copy is up to date)
At revision 390.
$ svn merge --reintegrate http://svn.example.com/repos/calc/branches/my-calc-branch
--- Merging differences between repository URLs into '.':
U button.c
U integer.c
U Makefile
U .
$ # build, test, verify, ...
$ svn commit -m "Merge my-calc-branch back into trunk!"
Sending .
Sending button.c
Sending integer.c
Sending Makefile
Transmitting file data ..
Committed revision 391.

Notice our use of the —reintegrate option this time around. The option is critical for reintegrating changes from a branch back into its original line of development—don’t forget it! It’s needed because this sort of “merge back” is a different sort of work than what you’ve been doing up until now. Now that your private branch is merged to trunk, you may wish to remove it from the repository:

$ svn delete http://svn.example.com/repos/calc/branches/my-calc-branch \
-m "Remove my-calc-branch."
Committed revision 392.

In Subversion 1.5, once a —reintegrate merge is done from branch to trunk, the branch is no longer usable for further work. It’s not able to correctly absorb new trunk changes, nor can it be properly reintegrated to trunk again. For this reason, if you want to
keep working on your feature branch, we recommend destroying it and then recreating it from the trunk:

Mergeinfo

The basic mechanism Subversion uses to track changesets—that is, which changes have been merged to which branches—is by recording data in properties. Specifically, merge data is tracked in the svn:mergeinfo property attached to files and directories.

$ cd my-calc-branch
$ svn propget svn:mergeinfo .
/trunk:341-390

There’s also a subcommand, svn mergeinfo, which can be helpful in seeing not only which changesets a directory has absorbed, but also which changesets it’s still eligible to receive. This gives a sort of preview of the next set of changes that svn merge will replicate to your branch.

$ cd my-calc-branch
# Which changes have already been merged from trunk to branch?
$ svn mergeinfo http://svn.example.com/repos/calc/trunk
r341
r342

r389
r390
# Which changes are still eligible to merge from trunk to branch?
$ svn mergeinfo http://svn.example.com/repos/calc/trunk --show-revs eligible
r391
r392
r393
r394
r395

The svn mergeinfo command requires a “source” URL (where the changes would be coming from), and takes an optional “target” URL (where the changes would be merged to). If no target URL is given, it assumes that the current working directory is the target. In the prior example, because we’re querying our branch working copy, the command assumes we’re interested in receiving changes to /branches/mybranch from the specified trunk URL. Another way to get a more precise preview of a merge operation is to use the
—dry-run option:

$ svn merge http://svn.example.com/repos/calc/trunk --dry-run
U integer.c
$ svn status
# nothing printed, working copy is still unchanged.

Undoing Changes

An extremely common use for svn merge is to roll back a change that has already been committed. Suppose you’re working away happily on a working copy of /calc/trunk, and you discover that the change made way back in revision 303, which changed integer.c, is completely wrong. All you need to do is to specify a reverse difference. (You can do this by specifying —revision 303:302, or by an equivalent —change -303.)

Traversing Branches

The svn switch command transforms an existing working copy to reflect a different branch. “Switching” a working copy that has no local modifications to a different branch results in the working copy looking just as it would if you’d done a fresh checkout of the directory.

$ svn info | grep URL
URL: http://svn.example.com/repos/calc/trunk
$ svn switch http://svn.example.com/repos/calc/branches/my-calc-branch
U integer.c
U button.c
U Makefile
Updated to revision 341.
$ svn info | grep URL
URL: http://svn.example.com/repos/calc/branches/my-calc-branch

Tags

A tag is just a “snapshot” of a project in time. Once again, svn copy comes to the rescue. If you want to create a snapshot of
/calc/trunk exactly as it looks in the HEAD revision, make a copy of it:

$ svn copy http://svn.example.com/repos/calc/trunk \
http://svn.example.com/repos/calc/tags/release-1.0 \
-m "Tagging the 1.0 release of the 'calc' project."
Committed revision 902.

This example assumes that a /calc/tags directory already exists. (If it doesn’t, you can create it using svn mkdir.) After the copy completes, the new release-1.0 directory is forever a snapshot of how the /trunk directory looked in the HEAD revision at the time you made the copy.
In Subversion, there’s no difference between a tag and a branch. Both are just ordinary directories that are created by copying. Just as with branches, the only reason a copied directory is a “tag” is because humans have decided to treat it that way: as long as nobody ever commits to the directory, it forever remains a snapshot. If people start committing to it, it becomes a branch.

Common Branching Patterns

1. Release Branches

Most software has a typical life cycle: code, test, release, repeat. There are two problems with this process. First, developers need to keep writing new features while quality assurance teams take time to test supposedly stable versions of the software. New work cannot halt while the software is tested. Second, the team almost always needs to support older, released versions of software; if a bug is discovered in the latest code, it most likely exists in released versions as well, and customers will want to get that bug fix without having to wait for a major new release. The typical procedure looks like this:

  1. Developers commit all new work to the trunk. Day-to-day changes are committed to /trunk: new features, bug fixes, and so on.
  2. The trunk is copied to a “release” branch. When the team thinks the software is ready for release (say, a 1.0 release), /trunk might be copied to /branches/1.0.
  3. Teams continue to work in parallel. One team begins rigorous testing of the release branch, while another team continues new work (say, for version 2.0) on /trunk. If bugs are discovered in either location, fixes are ported back and forth as necessary. At
    some point, however, even that process stops. The branch is “frozen” for final testing right before a release.
  4. The branch is tagged and released. When testing is complete, /branches/1.0 is copied to /tags/1.0.0 as a reference snapshot. The tag is packaged and released to customers.
  5. The branch is maintained over time. While work continues on /trunk for version 2.0, bug fixes continue to be ported from /trunk to /branches/1.0. When enough bug fixes have accumulated, management may decide to do a 1.0.1 release: /branches/1.0 is copied to /tags/1.0.1, and the tag is packaged and released.
    This entire process repeats as the software matures: when the 2.0 work is complete, a new 2.0 release branch is created, tested, tagged, and eventually released. After some years, the repository ends up with a number of release branches in “maintenance” mode, and a number of tags representing final shipped versions.

2. Feature Branches

It’s a temporary branch created to work on a complex change without interfering with the stability of /trunk. Unlike release branches (which may need to be supported forever), feature branches are born, used for a while, merged back to the trunk, and then ultimately deleted.
Here’s a great risk to working on a branch for weeks or months; trunk changes may continue to pour in, to the point where the two lines of development differ so greatly that it may become a nightmare trying to merge the branch back to the trunk. This situation is best avoided by regularly merging trunk changes to the branch. Make up a policy: once a week, merge the last week’s worth of trunk changes to the branch.

locale

运行locale指令得到当前系统编码设置的详细资料(UTF-8才是编码世界最通用的语言)。

LANG=en_US.UTF-8
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

locale 的命名规则为<语言>_<地区>.<字符集编码> ,如zh_CN.UTF-8,zh代表中文,CN代表大陆地区,UTF-8表示字符集。在locale 环境中,有一组变量,代表国际化环境中的不同设置:
1、 语言符号及其分类(LC_CTYPE)
2、 数字(LC_NUMERIC)
3、 比较和排序习惯(LC_COLLATE)
4、 时间显示格式(LC_TIME)
5、 货币单位(LC_MONETARY)
6、 信息主要是提示信息,错误信息, 状态信息, 标题, 标签, 按钮和菜单等(LC_MESSAGES)
7、 姓名书写方式(LC_NAME)
8、 地址书写方式(LC_ADDRESS)
9、 电话号码书写方式(LC_TELEPHONE)
10、度量衡表达方式(LC_MEASUREMENT)
11、默认纸张尺寸大小(LC_PAPER)
12、对locale自身包含信息的概述(LC_IDENTIFICATION)。

此外,还有两个特殊的设置

  • LANG
    LC_* 的默认值,是最低级别的设置,如果LC_* 没有设置,则使用该值。
  • LC_ALL
    如果该值设置了,则该值会覆盖所有LC_* 的设置值。注意,LANG 的值不受影响。

它们之间有一个优先级的关系:LC_ALL > LC_* > LANG

svn高级话题

1. Revision Specifiers

Revision numbers in Subversion are pretty straightforward—integers that keep getting larger as you commit more changes to
your versioned data. Besides the integer revision numbers, svn allows as input some additional forms of revision specifiers: revision keywords and revision dates.

  • HEAD
    The latest (or “youngest”) revision in the repository.
  • BASE
    The revision number of an item in a working copy. If the item has been locally modified, this refers to the way the item appears without those local modifications.
  • COMMITTED
    The most recent revision prior to, or equal to, BASE, in which an item changed.
  • PREV
    The revision immediately before the last revision in which an item changed.
    Here are some examples of revision keywords in action:
    $ svn diff -r PREV:COMMITTED foo.c
    # shows the last change committed to foo.c
    $ svn log -r HEAD
    # shows log message for the latest repository commit
    $ svn diff -r HEAD
    # compares your working copy (with all of its local changes) to the
    # latest version of that tree in the repository
    $ svn diff -r BASE:HEAD foo.c
    # compares the unmodified version of foo.c with the latest version of
    # foo.c in the repository
    $ svn log -r BASE:HEAD
    # shows all commit logs for the current versioned directory since you
    # last updated
    $ svn update -r PREV foo.c
    # rewinds the last change on foo.c, decreasing foo.c's working revision
    $ svn diff -r BASE:14 foo.c
    # compares the unmodified version of foo.c with the way foo.c looked
    # in revision 14

2. Ignoring Unversioned Items

In any given working copy, there is a good chance that alongside all those versioned files and directories are other files and directories that are neither versioned nor intended to be.

So Subversion provides two ways for telling it which files you would prefer that it simply disregard. One of the ways involves the use of Subversion’s runtime configuration system(see the section called “Runtime Configuration Area”), and therefore applies to all the Subversion operations that make use of that runtime configuration—generally those performed on a particular computer or by a particular user of a computer. The other way makes use of Subversion’s directory property support and is more tightly bound to the versioned tree itself, and therefore affects everyone who has a working copy of that tree. Both of the mechanisms use file patterns (strings of literal and special wildcard characters used to match against filenames) to decide which files to ignore.

The Subversion runtime configuration system provides an option, global-ignores, whose value is a whitespace-delimited collection of file patterns.When found on a versioned directory, the svn:ignore property is expected to contain a list of newline-delimited file patterns that Subversion should use to determine ignorable objects in that same directory. These patterns do not override those found in the global-ignores runtime configuration option, but are instead appended to that list. And it’s worth noting again that, unlike the global-ignores option, the patterns found in the svn:ignore property apply only to the directory on which that property is set, and not to any of its subdirectories.

Say you have the following output from svn status:

$ svn status calc
M calc/button.c
? calc/calculator

? calc/data.c
? calc/debug_log
? calc/debug_log.1
? calc/debug_log.2.gz
? calc/debug_log.3.gz

In this example, you have made some property modifications to button.c, but in your working copy, you also have some unversioned files. And you know that you aren’t interested in seeing those log files every time you run svn status. So you use svn propedit svn:ignore calc to add some ignore patterns to the calc directory. For example, you might add this as the new value of the svn:ignore property:

calculator
debug_log*

After you’ve added this property, you will now have a local property modification on the calc directory. But notice what else is different about your svn status output:

$ svn status
M calc
M calc/button.c
? calc/data.c

3. Sparse Directories

By default, most Subversion operations on directories act in a recursive manner. Subversion introduces a feature called sparse dir-
ectories (or shallow checkouts) that allows you to easily check out a working copy—or a portion of a working copy—more shallowly than full recursion, with the freedom to bring in previously ignored files and subdirectories at a later time. Here are the depth values that you can request for a given Subversion operation:

  • —depth empty
    Include only the immediate target of the operation, not any of its file or directory children.
  • —depth files
    Include the immediate target of the operation and any of its immediate file children.
  • —depth immediates
    Include the immediate target of the operation and any of its immediate file or directory children. The directory children will themselves be empty.
  • —depth infinity
    Include the immediate target, its file and directory children, its children’s children, and so on to full recursion.

4. Properties

In addition to versioning your directories and files, Subversion provides interfaces for adding, modifying, and removing versioned metadata on each of your versioned directories and files. We refer to this metadata as properties, and they can be thought of as two-column tables that map property names to arbitrary values attached to each item in your working copy. And the best part about these properties is that they, too, are versioned, just like the textual contents of your files. Subversion has reserved the set of properties whose names begin with svn: as its own. You should avoid creating custom properties for your own needs whose names begin with this prefix.

Just as files and directories may have arbitrary property names and values attached to them, each revision as a whole may have
arbitrary properties attached to it.The main difference is that revision properties are not versioned.

  • svn propset
$ svn propset copyright '(c) 2006 Red-Bean Software' calc/button.c
property 'copyright' set on 'calc/button.c'

$ svn propset license -F /path/to/LICENSE calc/button.c
property 'license' set on 'calc/button.c'

In addition to the propset command, the svn program supplies the propedit command. This command uses the configured editor program to add or modify properties.

$ svn propset copyright '(c) 2006 Red-Bean Software' calc/*
property 'copyright' set on 'calc/Makefile'
property 'copyright' set on 'calc/button.c'
property 'copyright' set on 'calc/integer.c'

The svn proplist command will list the names of properties that exist on a path. Once you know the names of the prop-
erties on the node, you can request their values individually using svn propget.

$ svn proplist calc/button.c
Properties on 'calc/button.c':
copyright
license
$ svn propget copyright calc/button.c
(c) 2006 Red-Bean Software

The last property-related subcommand is propdel.

$ svn propdel license calc/button.c
property 'license' deleted from 'calc/button.c'.

Remember those unversioned revision properties? You can modify those, too, using the same svn subcommands that we just described. Simply add the —revprop command-line parameter and specify the revision whose property you wish to modify. Since revisions are global, you don’t need to specify a target path to these property-related commands so long as you are positioned in a working copy of the repository whose revision property you wish to modify. Otherwise, you can simply provide the URL of any path in the repository of interest (including the repository’s root URL). For example, you might want to replace the commit log message of an existing revision. If your current working directory is part of a working copy of your repository, you can simply run the svn propset command with no target path:

$ svn propset svn:log '* button.c: Fix a compiler warning.' -r11 --revprop
property 'svn:log' set on repository revision '11'

But even if you haven’t checked out a working copy from that repository, you can still effect the property change by providing the repository’s root URL:

$ svn propset svn:log '* button.c: Fix a compiler warning.' -r11 --revprop \
http://svn.example.com/repos/project
property 'svn:log' set on repository revision '11'

svn日常工作流程

The typical work cycle looks like this:

  1. Update your working copy.
    • svn update
  2. Make changes.
    • svn add
    • svn delete
    • svn copy
    • svn move
  3. Examine your changes.
    • svn status
    • svn diff
  4. Possibly undo some changes.
    • svn revert
  5. Resolve conflicts (merge others’ changes).
    • svn update
    • svn resolve
  6. Commit your changes.
    • svn commit

svn status

To get an overview of your changes before committing, you’ll use the svn status command. Here are a few examples of the most common status codes that svn status can return. (Note that the text following # is not actually printed by svn status.)

? scratch.c         # file is not under version control
A stuff/loot/bloo.h # file is scheduled for addition
C stuff/loot/lump.c # file has textual conflicts from an update
D stuff/fish.c # file is scheduled for deletion
M bar.c # the content in bar.c has local modifications

Command svn status does not contact the repository. Instead, they compare the metadata in the .svn directory with the working copy. There is the —show-updates (-u) option, which contacts the repository and adds information about things that are out of date:

$ svn status -u -v
M * 44 23 sally README
M 44 20 harry bar.c
* 44 35 harry stuff/trout.c
D 44 19 ira stuff/fish.c
A 0 ? ? stuff/things/bloo.h
Status against revision: 46

svn status also has a —verbose (-v) option, which will show you the status of every item in your working copy, even if it has not been changed. Notice the two asterisks: if you were to run svn update at this point, you would receive changes to README and trout.c. This tells you some very useful information—you’ll need to update and get the server changes on README before you commit, or the repository will reject your commit for being out of date.

svn revert

Suppose while viewing the output of svn diff you determine that all the changes you made to a particular file are mistakes. Maybe you shouldn’t have changed the file at all, or perhaps it would be easier to make different changes starting from scratch.Subversion reverts the file to its premodified state by overwriting it with the cached “pristine” copy from the .svn area. But also note that svn revert can undo any scheduled operations

$ svn status README
$ svn delete README
D README
$ svn revert README
Reverted 'README'
$ svn status README

Resolve Conflicts (Merging Others’ Changes)

We’ve already seen how svn status -u can predict conflicts. Suppose you run svn update and some interesting things occur:

$ svn update
U INSTALL
G README
Conflict discovered in 'bar.c'.
Select: (p) postpone, (df) diff-full, (e) edit,
(h) help for more options:

The U and G codes are no cause for concern; those files cleanly absorbed changes from the repository. The files marked with U contained no local changes but were Updated with changes from the repository. The G stands for merGed, which means that the file had local changes to begin with, but the changes coming from the repository didn’t overlap with the local changes. But the next two lines are part of a feature called interactive conflict resolution. This means that the changes from the server overlapped with your own, and you have the opportunity to resolve this conflict. The most commonly used options are displayed, but you can see all of the options by typing h:

(p) postpone - mark the conflict to be resolved later
(df) diff-full - show all changes made to merged file
(e) edit - change merged file in an editor
(r) resolved - accept merged version of file
(mf) mine-full - accept my version of entire file (ignore their changes)
(tf) theirs-full - accept their version of entire file (lose my changes)
(l) launch - launch external tool to resolve conflict
(h) help - show this list

Let’s briefly review each of these options before we go into detail on what each option means.

(p)ostpone
Leave the file in a conflicted state for you to resolve after your update is complete.
(d)iff
Display the differences between the base revision and the conflicted file itself in unified
diff format.
(e)dit
Open the file in conflict with your favorite editor, as set in the environment
variable EDITOR.
(r)esolved
After editing a file, tell svn that you've resolved the conflicts in the file and that
it should accept the current contents—basically that you've “resolved” the conflict.
(m)ine-(f)ull
Discard the newly received changes from the server and use only your local changes for
the file under review.
(t)heirs-(f)ull
Discard your local changes to the file under review and use only the newly received
changes from the server.
(l)aunch
Launch an external program to perform the conflict resolution. This requires a bit of
preparation beforehand.
(h)elp
Show the list of all possible commands you can use in interactive conflict resolution.

Before deciding how to attack a conflict interactively, odds are that you’d like to see exactly
what is in conflict, and the diff command (d) is what you’ll use for this:

Select: (p) postpone, (df) diff-full, (e) edit,
(h)elp for more options : d
--- .svn/text-base/sandwich.txt.svn-base Tue Dec 11 21:33:57 2007
+++ .svn/tmp/tempfile.32.tmp Tue Dec 11 21:34:33 2007
@@ -1 +1,5 @@
-Just buy a sandwich.
+<<<<<<< .mine
+Go pick up a cheesesteak.
+=======

+Bring me a taco!
+>>>>>>> .r32

The first line of the diff content shows the previous contents of the working copy (the BASE revision), the next content line is your change, and the last content line is the change that was just received from the server (usually the HEAD revision).

When you postpone a conflict resolution, svn typically does three things to assist you in noticing and resolving that conflict:

  1. Subversion prints a C during the update and remembers that the file is in a state of conflict.
  2. If Subversion considers the file to be mergeable, it places conflict markers—special strings of text that delimit the “sides” of the conflict—into the file to visibly demonstrate the overlapping areas. (Subversion uses the svn:mime-type property to decide whether a file is capable of contextual, line-based merging)
  3. For every conflicted file, Subversion places three extra unversioned files in your working copy:
  • filename.mine
    This is your file as it existed in your working copy before you updated your working
    copy—that is, without conflict markers. This file has only your latest changes in it.
  • filename.rOLDREV
    This is the file that was the BASE revision before you updated your working copy. That
    is, the file that you checked out before you made your latest edits.
  • filename.rNEWREV
    This is the file that your Subversion client just received from the server when you updated your working copy. This file corresponds to the HEAD revision of the repository.

If you’ve postponed a conflict, you need to resolve the conflict before Subversion will allow you to commit your changes. You’ll do this with the svn resolve command and one of several arguments to the —accept option.

  • If you want to choose the version of the file that you last checked out before making your edits, choose the base argument.
  • If you want to choose the version that contains only your edits, choose the mine-full argument.
  • If you want to choose the version that your most recent update pulled from the server, choose the theirs-full argument.
  • However, if you want to pick and choose from your changes and the changes that your update fetched from the server, merge the conflicted text “by hand” (by examining and editing the conflict markers within the file) and then choose the working argument.

svn resolve removes the three temporary files and accepts the version of the file that you specified with the —accept option, and Subversion no longer considers the file to be in a state of conflict:

$ svn resolve --accept working sandwich.txt
Resolved conflicted state of 'sandwich.txt'

svn基本操作

1. help

svn help subcommand will describe the syntax, options, and behavior of the subcommand.

2. import

The svn import command is a quick way to copy an unversioned tree of files into a repository, creating intermediate directories as necessary. You typically use this when you have an existing tree of files that you want to begin tracking in your Subversion repository. 例如,假设svn服务器上存在以下目录https://var/svn/newrepos/,本地机器上存在md5目录,并且md5目录下存在md5.c和md5.h两个文件。现在想把md5这个新的项目导入到svn服务器,使其具有版本管理功能。

svn import md5 https://var/svn/newrepos/md5 -m 'Initial md5'

Note that after the import is finished, the original tree is not converted into a working copy. To start working, you still need to svn checkout a fresh working copy of the tree.

3. checkout

Most of the time, you will start using a Subversion repository by doing a checkout of your project. Checking out a repository creates a “working copy” of it on your local machine.

svn checkout https://var/svn/newrepos/md5
svn co https://var/svn/newrepos/md5

While you can certainly check out a working copy with the URL of the repository as the only argument, you can also specify a directory after your repository URL. This places your working copy in the new directory that you name.

svn co https://var/svn/newrepos/md5 my_md5

That will place your working copy in a directory named my_md5 instead of a directory named md5 as we did previously. The directory my_md5 will be created if it doesn’t already exist.

4. Authenticating As a Different User

If you’re working in a shared working copy such as a system configuration directory or a web server document root. In this case,
just pass the —username option on the command line, and Subversion will attempt to authenticate as that user, prompting you for a password if necessary.

svn list https://var/svn/newrepos/md5 --username xiaoming

5. svn export

If you’re building a release and wish to bundle up your files from Subversion but don’t want those pesky .svn directories in the way, you can use svn export to create a local copy of all or part of your repository sans .svn directories.

$ svn export http://svn.example.com/svn/repos1 -r 1729
# Exports revision r1729

6. Examining History

Several commands can provide you with historical data from the repository:

svn log

Shows you broad information: log messages with date and author information attached to revisions and which paths changed in each revision. If you wish to see a different range of revisions in a particular order or just a single revision, pass the —revision (-r) option:

$ svn log -r 5:19 # shows logs 5 through 19 in chronological order
$ svn log -r 19:5 # shows logs 5 through 19 in reverse order
$ svn log -r 8 # shows log for revision 8

In verbose mode, svn log will include a list of changed paths in a revision in its output:

$ svn log -r 8 -v
------------------------------------------------------------------------

r8 | sally | 2008-05-21 13:19:25 -0500 (Wed, 21 May 2008) | 1 line
Changed paths:
M /trunk/code/foo.c
M /trunk/code/bar.h
A /trunk/code/doc/README
Frozzled the sub-space winch.

svn diff

There are three distinct uses of svn diff:

  • Invoking svn diff with no options will compare your working files to the cached “pristine” copies in the .svn area
  • If a single —revision (-r) number is passed, your working copy is compared to the specified revision in the repository
  • If two revision numbers, separated by a colon, are passed via —revision (-r), the two revisions are directly compared:
    svn diff -r 2:3 rules.txt
  • If a “—change(-c) ARG” is passed, it shows the change made by revision ARG(like -r ARG-1:ARG). If ARG is negative this is
    like -r ARG:ARG-1

svn cat

Retrieves a file as it existed in a particular revision number and displays it on your screen

svn list

Displays the files in a directory for any given revision

CPU点滴

1. register memory architecture

One of the operands for ADD operation may be in memory, while the other is in a register. Examples are IBM 360 and Intel x86.

2. register plus memory architecture

Both operands for ADD operation may be in memory or in registers, or in combinations.

3. load/store architecture

Both operands for ADD operation must be in registers. In computer engineering a load/store architecture only allows memory to be accessed by load and store operations, and all values for an operation need to be loaded from memory and be present in registers. Following the operation, the result needs to be stored back to memory. For instance, in a load/store approach both operands for an ADD operation must be in registers. This differs from a register memory architecture in which one of the operands for the ADD operation may be in memory, while the other is in a register. RISC systems such as PowerPC, SPARC, ARM or MIPS use the load/store architecture.

CPU访问外部设备

整个计算机由CPU、内存和外部设备组成。当CPU要对内存中的内容进行读写操作时,地址总线负责传输地址,控制总线指明是读取操作还是写入操作,数据总线则用来传输写入内存或从内存读出的信息。而CPU与外部设备的交互,则是通过CPU读写外部设备上的寄存器实现的,外设寄存器也称为“I/O端口”。
我们知道,内存中的每个存储单元都有一个地址与其对应,CPU根据这个地址就可以指定自己想要访问的存储单元。那外部设备呢?外部设备中的寄存器也需要有一个地址与之相对应,才能让CPU根据寄存器的地址来区分不同设备之间的寄存器和同一个设备中的不同寄存器。既然,外部设备中的寄存器也需要有一个地址,那么现在的问题是外部设备中的寄存器地址与内存地址的关系是什么呢。
1、独立编址(ISOLATED I/O): 也称为“I/O端口”方式,外部设备中的寄存器(I/O端口)地址与内存地址是相互独立、互不影响的,它们分别位于不同的地址空间内。即I/O端口地址位于I/O地址空间内,而内存地址则位于内存地址空间内。不同的地址空间,这个如何理解呢。举个例子,I/O地址空间内存在0xff这样的地址,内存地址空间也可以存在0xff这样的地址,它们两个互不影响。那当地址总线上出现0xff这个地址时,CPU到底是想访问内存还是想访问I/O端口呢,这个是由I/O读控制线、I/O写控制线、内存读控制线、内存写控制线来决定的。如果CPU访问的是I/O端口,那么I/O读控制线或者I/O写控制线被激活;如果CPU访问的是内存,那么内存读控制线或者内存写控制线被激活。所以,采用独立编址方式,对于某个地址(例如0xff)而言,它既可以存在于I/O地址空间内,也可以位于内存地址空间内。在采用独立编址的体系架构下,CPU通过MOV指令来访问内存,而对I/O端口的访问则是通过IN和OUT指令来完成的。IN指令和OUT指令的具体例子如下:

IN AL, 21H  // 从21H端口读取一字节数据到AL寄存器
OUT 21H, AL // 将寄存器AL的值写入21H端口

/*
汇编语言中的数字:
二进制 11111111B 00001111B // 数字后加B
十进制 255 16
十六进制 0FFH 0FH // 数字后加HS
另外,当十六进制数的第一个字符是字母时,必须在第一个字符之前添加一个0
*/

32位处理器的内存地址空间大小是4G,而独立编址的I/O地址空间的大小为64K,即它由65536个8bit的I/O端口组成。I/O端口的编号从 0x0000到0xFFFF,有16位,80x86架构用低16位地址线A0-A15来寻址。连续两个8bit的端口可以组成一个16bit的端口,连续4个组成一个 32bit的端口。采用独立编址的典型例子是intel 80x86。

2、统一编址(Memory-Mapped I/O):也称为“I/O内存”方式。外部设备中的寄存器和内存中的存储单元被同等看待,每个I/O端口占用一个内存存储单元的地址,将内存地址的一部分划出来用作IO地址空间。I/O端口占用了内存的地址空间,使内存的存储量容量减小。对于一个32位系统来说,它的4G的地址空间被内存和外部设备中的寄存器共同瓜分。采用统一编址方式,CPU访问外部设备IO端口时,不再需要额外的IN和OUT指令,CPU只需要使用同一套指令就可以访问内存或者IO端口。arm,powerpc在这一类的嵌入式处理器中采用统一编址方式。

linux内核list分析三:哈希链表

linux内核里面的双向循环链表和哈希链表有什么不同呢?1、双向循环链表是循环的,哈希链表不是循环的 2、双向循环链表不区分头结点和数据结点,都用list_head表示,而哈希链表区分头结点(hlist_head)和数据结点(hlist_node)。与哈希链表有关的两个数据结构如下:

struct hlist_head { 
struct hlist_node *first; //指向每一个hash桶的第一个结点的指针
};
struct hlist_node {
struct hlist_node *next; //指向下一个结点的指针
struct hlist_node **pprev; //指向上一个结点的next指针的指针
};

1、哈希链表为什么要区分头结点和数据结点?

头结点和数据结点如果都使用list_head的话,那岂不是更容易实现。内核list.h中描述得很明白:

/*
* Double linked lists with a single pointer list head.
* Mostly useful for hash tables where the two pointer list head is
* too wasteful.
* You lose the ability to access the tail in O(1).
*/

意思是说这种双向链表的头结点只有一个指针成员(即struct hlist_node *first),它主要使用在哈希表中。因为哈希表会有很多表项,每个表项如果使用list_head这样含有两个指针成员的数据结构的话,会造成内存空间的浪费。所以,为了尽可能的减少内存空间的浪费,就要使数据结构变得稍微复杂一些,鱼和熊掌不可兼得啊。

2、hlist_node的pprev成员为什么是struct hlist_node **类型的?

如果hlist_node的定义是下面这样的话

struct hlist_node { 
struct hlist_node *next;
struct hlist_node *prev;
};

第一个数据结点的prev成员应该指向头结点,但是因为prev成员指向的是hlist_node的数据类型的指针,而头结点的数据类型是hlist_head,所以无法实现。为了解决这样的问题,才有了下面的hlist_node的定义

struct hlist_node { 
struct hlist_node *next;
struct hlist_node **pprev;
};


在上图中,第一个数据结点的pprev成员(数据类型struct hlist_node **)指向头结点的first成员(数据类型struct hlist_node *),第二个数据结点的pprev成员(数据类型struct hlist_node **)指向第一个数据结点的next成员(数据类型struct hlist_node *),从而对于第一个数据结点的操作和非第一个数据结点的操作就没有区别了,都统一起来了。这样就不用对第一个数据结点进行特殊处理了,为编写代码带来了极大的好处,这种设计有点小艺术。

其他的hlist的初始化、插入、删除、遍历请自行参阅list.h源文件,源文件是最好的老师。

linux内核list分析二:双向循环链表

大学时代,我们就已经学过链表了,例如下面的例子

struct person
{
int height;
int weight;
struct person *prev, *next;
};

// 查找指定的person是否存在
int person_exists(struct person *head, struct person in_person)
{

struct person *p;

for(p = head->next; p != head; p = p->next)
{
if(p->height == in_person.height && p->weight == in_person.weight)
return 1;
}

return 0;
}

上面的代码定义了struct person这样的链表,通常会实现该链表的插入、删除、查找等操作。现在因为业务需求,又需要struct animal这样的链表,同样,我们也会实现该链表的插入、删除、查找等操作。

struct animal
{
int legs;
struct animal *prev, *next;
};

// 查找指定的animal是否存在
int animal_exists(struct animal *head, struct animal in_animal)
{

struct animal *p;

for(p = head->next; p != head; p = p->next)
{
if(p->legs == in_animal.legs)
return 1;
}

return 0;
}

那如果再出现其他的数据类型也需要使用到链表的操作,我们也要为新增的数据类型编写新的链表插入、删除、查找等操作,这样就出现了大量的冗余代码。其实,查找person与anmial是否存在的代码处理逻辑都是一样的,只是数据类型不一样而已。所以为了解决这个问题,linux内核中把与链表有关的操作抽象出来,其他需要使用链表操作的数据类型只要使用内核定义的链表就OK,无需自己再开发与链表的基本操作相关的代码。

linux内核双向循环链表仅仅使用了一个数据结构struct list_head,即链表的头结点和数据结点都是使用struct list_head表示

struct list_head{ 
struct list_head *next, *prev;
};

让我们看看使用了struct list_head的struct animal是怎样的。下面代码中的INIT_LIST_HEAD、list_add、list_for_each_entry都是linux内核实现的。它的具体实现可以参考include/linux/list.h

struct animal
{
int legs;
struct list_head list;
};
// 初始化链表头结点
struct list_head animal_list;
INIT_LIST_HEAD(&animal_list);

// 初始化struct animal
struct animal animal1;
animal1.legs = 4;
INIT_LIST_HEAD(&animal1.list);

// 将animal1加入链表
list_add(&animal1, &animal_list);

// 加入其它animal到链表
.....

// 遍历 animal_list
struct animal *p_animal;
list_for_each_entry(p_animal, &animal_list, list)
{
printf("legs = %d\n", p_animal->legs);
}

它的内存布局如下:

linux内核list分析一:前言

链表list是linux内核最经典的数据结构之一,不过在深入学习链表的实现之前,需要了解几个知识:offsetof、typeof、container_of

1、offsetof

offsetof的作用是返回结构体中的某个成员在该结构体中的偏移量,请看下面的例子:

struct person
{
int height;
int weight;
};

printf("%u\n", offsetof(struct person, height)); // 0
printf("%u\n", offsetof(struct person, weight)); // 4

而offsetof的真实面目是

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

这里巧妙的利用了0地址。也许有人会产生疑问,怎么可能会对0地址进行操作呢?为了更容易理解,下面都使用struct person为例。其实,0是一个具体的常量值,它是一个地址,而(struct person *)0则是一个指针,而且是一个指针常量(即指针本身是常量,但它指向的地址里的内容可以改变)。只要我们不去对一个空指针进行读写,就不会存在非法访问内存的问题,例如:

//  读取操作
struct person *xiao_hua = 0;
struct person tmp = *xiao_hua; // error

// 写入操作
struct person *xiao_hua = 0;
struct person tmp = {180, 60};
*xiaohua = tmp; // error

那么,其他的操作都是OK的。

offsetof(struct person, weight) 经过宏替换后变为 ((size_t) &((struct person )0)->weight)
1、(struct person
)0 表示 一个指向struct person结构的指针,虽然这个struct person结构不存在,但只要不去对它进行读写就OK
2、(struct person )0)->weight 表示 1中的指针所指向的那个struct person结构的weight成员
3、&((struct person
)0)->weight 表示 1中的指针所指向的那个struct person结构的weight成员的地址
4、weight成员的地址 减去 它所在的struct person结构的地址,就可以得出weight在struct person结构中的偏移量,但是此时,struct person结构的地址为0,所以weight成员的地址就是weight在struct person结构中的偏移量

2、typeof

typeof关键字是C语言中的一个新扩展,这个特性在linux内核中应用非常广泛。
typeof的参数可以是两种形式:表达式或类型。

(1) 表达式的例子:

//  以下示例声明了int类型的var变量,因为表达式foo()是int类型的。由于表达式不会被执行,所以不会调用foo函数。
extern int foo();
typeof(foo()) var; // 等价于 int var;

(2) 类型的例子:

typeof(int *) a,b; // 等价于 int *a,*b;

3、container_of

它的作用是根据一个结构体变量中的一个域成员变量的指针来获取指向整个结构体变量的指针。

#define container_of(ptr, type, member) ({                      \
const typeof ( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( ( char *)__mptr - offsetof(type,member) );})


struct person xiao_hua = {180, 60};
struct person *p_xiao_hua = container_of(&xiao_hua.weight, struct person, weight);
printf("%d\n", p_xiao_hua->height); // 180

container_of(&xiao_hua.weight, struct person, weight) 宏替换后的结果为

const typeof ( ((struct person *)0)->weight ) *__mptr = (&xiao_hua.weight);
(struct person *)( ( char *)__mptr - offsetof(struct person,weight) );})

第一个语句中的‘typeof ( ((struct person )0)->weight )’ 其实就是 ‘int’,所以第一个语句就是’const int __mptr = (&xiao_hua.weightr)’
第二个语句将weight的地址减去weight在struct person中的偏移量就得到了struct person结构变量的地址。

这里使用了一个中间变量__mptr,也许我们会质疑,它是多余的。但是,请看下面的情况

#define container_of(ptr, type, member) ({                      \
(type *)( ( char *)ptr - offsetof(type,member) );})


struct person xiao_hua = {180, 60};
struct person *p_xiao_hua = container_of(&xiao_hua, struct person, weight); // 错误使用container_of
printf("%d\n", p_xiao_hua->height);

所以中间变量__mptr这里起到了提醒开发者的功能。如果开发者传入的ptr指针指向的类型,与结构体中成员的类型不符,编译器在这里会打印一条warning,提示开发者可能存在的错误。

hexo和github pages搭建个人博客

搭建个人博客需要用到两个软件git和node.js,所以请先安装完成这两个软件。本教程仅适用于windows平台。

一、 github的配置

我们在本地写完博客以后,会提交到github上。github提供了两种认证方式:用户名密码和ssh。如果使用用户名和密码的认证方式,那么我们每次提交到github的时候都会要求我们输入用户名和密码,这种方式用户体验不好。所以,我们使用ssh认证方式,即在本地生成一对私钥和公钥,然后把公钥添加到自己在github的账号信息里,这样提交博客到github上时会自动完成认证过程。

1.1 检查ssh密钥是否已经存在

首先,我们需要检查自己的机器上是否已经存在ssh密钥,打开git bash并输入以下命令

$ ls -al ~/.ssh
# Lists the files in your .ssh directory, if they exist

如果存在 id_rsa.pub 和 id_rsa这两个文件,说明之前已经生成过ssh密钥,请忽略步骤1.2

1.2 生成ssh密钥

打开git bash,执行以下命令,别忘了将邮箱地址替换成自己在github上的邮箱地址。

ssh-keygen -t rsa -C "your_email@example.com"
# Creates a new ssh key, using the provided email as a label
Generating public/private rsa key pair.

让密钥文件保存在默认位置,直接按回车键即可。

Enter file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter]

输入一个使用密钥文件时需要输入的密码(不然每个人都可以随便使用此密钥文件来访问你的github)

Enter passphrase (empty for no passphrase): [Type a passphrase]
Enter same passphrase again: [Type passphrase again]

输入密码之后,界面会输出成功生成密钥的信息

Your identification has been saved in /Users/you/.ssh/id_rsa.
Your public key has been saved in /Users/you/.ssh/id_rsa.pub.
The key fingerprint is:
01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db your_email@example.com

1.3 把公钥添加到自己的github账号

打开公钥文件/Users/you/.ssh/id_rsa.pub,复制一下里面的全部内容。需要注意的一点是,复制公钥的时候不能多了换行符或者空格,否则添加公钥时会失败。然后,参见github的添加ssh密钥的官方文档的“Step 4: Add your SSH key to your account”章节,将自己的公钥添加到github账号。

1.4 测试是否能ssh连接到github

打开git bash,输入

ssh -T git@github.com
# Attempts to ssh to GitHub

你会看到以下信息

The authenticity of host 'github.com (207.97.227.239)' can't be established.
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)?

输入yes,看到以下信息说明ssh配置成功

Hi username! You've successfully authenticated, but GitHub does not
provide shell access.

二、 github pages的配置

首先,github是一个具有版本管理功能的代码仓库,我们可以在上面建立多个项目。每一个项目会有自己的一个默认主页,主页内只会列出项目的源文件。这对于一个新手来说,不知从何入手。人们需要有一个对这个项目的具体介绍的主页,以帮助第一次接触这个项目的人对此项目有一个大概的了解。所以我们应该自定义项目的主页,来代替原本只是列出项目源文件的主页。github pages就是完成这样的功能的。github pages能够建立两种类型的网站,第一种是为每一个github用户或者组织建立的网站,用来介绍该用户或组织,这种网站每个用户或者组织只能建立一个;第二种是为用户或者组织的github上的项目建立的网站,用来介绍项目的情况,这种网站每个用户或者组织能建立多个。我们搭建个人博客需要建立的网站是第一种网站,所以需要在github上新建一个项目,这个项目的名字有特殊的要求,即项目的名字必须符合“username.github.io”这种格式,其中username是自己在github上的账户名称。

三、 git的配置

Git会根据用户的名字和邮箱来记录提交,GitHub也是用这些信息来做权限的处理。所在在使用git向github提交文件之前,需要先在git中设置自己的github用户名和邮箱。

$ git config --global user.name  "github_user_name"
$ git config --global user.email "your_email@youremail.com"

四、 hexo的配置

hexo是一款基于node.js的静态博客框架。

4.1 安装hexo

打开git bash,执行以下命令

$ npm install hexo-cli -g

4.2 新建并初始化博客项目blog

打开git bash,执行以下命令

$ hexo init blog
$ cd blog
$ npm install

4.3 在本地浏览博客界面

打开git bash,执行以下命令

$ hexo server

然后,打开浏览器,输入网址http://127.0.0.1:4000/,看到如下图所示的经典的“hello world”,说明配置成功。我们看到的“hello world”文章是hexo为我们发出的第一篇博客。

五、 利用hexo写一篇博客并发布到github

5.1 新建一篇博客

在git bash中将当前目录切换到上一步骤中的blog目录

$ hexo new 'your_first_blog_title'

这时,会生成blog/source/_posts/your_first_blog_title.md文件

5.2 编辑博客

用自己喜欢的编辑工具打开your_first_blog_title.md文件,利用markdown语法来编辑博客,保存。

5.3 修改项目配置文件

打开blog/_config.yml,修改以下内容

# Deployment
## Docs: http://hexo.io/docs/deployment.html
deploy:
type: git
repo: git@github.com:user_name/user_name.github.io.git
branch: master

5.4 生成静态网站

$ hexo generate

5.5 发布到github

$ hexo deploy

5.6 欣赏

在浏览器中输入网址https://user_name.github.io,欣赏一下自己刚刚发布的博客吧!

六、 ssh-agent的配置

在上一步骤中提交文件到github的时候,会让我们输入访问密钥证书的密码。每次都要这样,怎么破呢?ssh-agent可以帮助我们解决这个问题。我们需要在打开git bash的时候自动运行ssh-agent,所以需要把下面的shell代码复制到~/.profile或者~/.bashrc文件中。

# Note: ~/.ssh/environment should not be used, as it
# already has a different purpose in SSH.

env=~/.ssh/agent.env

# Note: Don't bother checking SSH_AGENT_PID. It's not used
# by SSH itself, and it might even be incorrect
# (for example, when using agent-forwarding over SSH).

agent_is_running() {
if [ "$SSH_AUTH_SOCK" ]; then
# ssh-add returns:
# 0 = agent running, has keys
# 1 = agent running, no keys
# 2 = agent not running
ssh-add -l >/dev/null 2>&1 || [ $? -eq 1 ]
else
false
fi
}

agent_has_keys() {
ssh-add -l >/dev/null 2>&1
}

agent_load_env() {
. "$env" >/dev/null
}

agent_start() {
(umask 077; ssh-agent >"$env")
. "$env" >/dev/null
}

if ! agent_is_running; then
agent_load_env
fi

# if your keys are not stored in ~/.ssh/id_rsa or ~/.ssh/id_dsa, you'll need
# to paste the proper path after ssh-add
if ! agent_is_running; then
agent_start
ssh-add
elif ! agent_has_keys; then
ssh-add
fi

unset env

保存文件后,在第一次打开git bash的时候,会提示我们输入访问ssh密钥的密码,我们输入正确的密码之后,以后再提交博客到github的时候就是完全自动化的。

ctags

Ability

ctags可以使我们在函数调用和函数定义之间来回跳转,方便阅读源代。The ability to jump from the current source file to definitions of functions and structures in other files. A tag is an identifier that appears in a “tags” file. It is a sort of label that can be jumped to. For example: In C programs each function name can be used as a tag. The “tags” file has to be generated by a program like ctags, before the tag commands can be used.

原理

ctags分析源代码中的函数、变量、宏定义等信息,将这些tag的名字和它们所在的文件,以及如何通过命令跳转到这些tag等信息保存到名为tags的文件中。

用法

  1. ctags -R *
    要想使用ctags带来的在函数调用和函数定义之间来回跳转的功能,首先要生成名为tags的文件。在shell环境中切换到源代码根目录,输入命令ctags -R *,即可在源代码根目录下生成tags文件。

  2. vim -t <tag>
    To search for a specific tag and open Vim to its definition, run the above command in your shell.

  3. Open any source file in Vim and use the following basic commands:

Keyboard command Action
Ctrl-] Jump to the tag underneath the cursor
Ctrl-t Jump back up in the tag stack
:tag <tag> Position the cursor on the tag
:ts <tag> Search for a particular tag
:tags Show where you are in the tag stack
:tn Go to the next definition for the last tag
:tp Go to the previous definition for the last tag

注意

运行vim的时候,必须在tags文件所在的目录下运行,因为vim启动时会在当前目录内寻找tags文件并加载它。否则,运行vim的时候还要用:set tags=命令设定tags文件的路径,这样vim才能找到tags文件。