Wednesday, June 20, 2007
Alignment
Matlab: Diminishing the Size of a Matrix
You can delete rows and columns from a matrix by assigning the empty array [] to those rows or columns. Start with
A = magic(4)
A =
16 2 3 13
5 11 10 8
9 7 6 12
4 14 15 1
Then, delete the second column of A using
A(:, 2) = []
This changes matrix A to
A =
16 3 13
5 10 8
9 6 12
4 15 1
If you delete a single element from a matrix, the result isn't a matrix anymore. So expressions like
A(1,2) = []
result in an error. However, you can use linear indexing to delete a single element, or a sequence of elements. This reshapes the remaining elements into a row vector:
A(2:2:10) = []
results in
A =
16 9 3 6 13 12 1
C++ Design Tricks
2. Generally, if a destructor is necessary to reclaim memory, then the default for copy assignment and copy construction are not acceptable.
Tuesday, June 19, 2007
Chapter 1. Introduction
function signatures
1. const is part of the function signature. Whether a member function is an accessor (via the const at the end) or a mutator is part of the signature.
2. default parameters are specified in the interface only. They are omitted in the implementation.
返回值是否属于signature的一部分呢?
explicit constructor
Why?
In C++, a one parameter constructor defines an implicit type conversion, in which a temporary object is created that makes an assignment (or parameter to a function) compatible.
IntCell obj;
obj = 37;
will be transformed into:
IntCell temporary = 37; //initialization, one-parameter constructor will be invoked
obj = temporary; //assignment
The construction of the temporary can be performed by using the one-parameter constructor. The use of explicit means that a one-parameter constructor cannot be used to generate an implicit temporary.
Pointer to Member functions
函数指针不能被赋值为成员函数的地址,即使返回类型和参数表完全匹配. 成员函数有一个非成员函数不具有的属性--它的类 (its class). 成员函数指针首先必须被绑定到一个对象或者指针上,才能得到被调用的对象的this指针,然后才调用指针所指的成员函数.
Q: 如何获得成员函数的地址?
A: &Class_name::member_function_name; 必须赋给成员函数的指针.
成员函数的指针和指向类数据成员的指针都要求扩展的书法, 它要考虑类的类型.
指向成员操作符的指针 (.* or ->*)
int (Screen::*pmfi) () = &Screen::height;
Screen & (Screen::*pmfS) (const Screen &) = &Screen::copy;
//直接调用成员函数
if ( myScreen.height() == bufScreen->height())
bufScreen->copy (myScreen);
//通过成员指针的等价调用
if( (myScreen.*pmfi) () == (bufScreen->*pmfi) () )
(bufScreen->*pmfS) (myScreen);
静态类成员的指针是普通指针, 因为它们没有this指针.
Garbage Collection Algorithm
A Simple Garbage Collector for C++ - Choosing a Garbage Collection Algorithm
(Page 3 of 12 )
Before implementing a garbage collector for C++, it is necessary to decide what garbage collection algorithm to use. The topic of garbage collection is a large one, having been the focus of serious academic study for many years. Because it presents an intriguing problem for which there is a variety of solutions, a number of different garbage collection algorithms have been designed. It is far beyond the scope of this book to examine each in detail. However, there are three archetypal approaches: reference counting, mark and sweep, and copying. Before choosing an approach, it will be useful to review these three algorithms.
Reference CountingIn reference counting, each dynamically allocated piece of memory has associated with it a reference count. This count is incremented each time a reference to the memory is added and decremented each time a reference to the memory is removed. In C++ terms, this means that each time a pointer is set to point to a piece of allocated memory, the reference count associated with that memory is incremented. When the pointer is set to point elsewhere, the reference count is decremented. When the reference count drops to zero, the memory is unused and can be released.
The main advantage of reference counting is simplicity—it is easy to understand and implement. Furthermore, it places no restrictions on the organization of the heap because the reference count is independent of an object’s physical location. Reference counting adds overhead to each pointer operation, but the collection phase is relatively low cost. The main disadvantage is that circular references prevent memory that is otherwise unused from being released. A circular reference occurs when two objects point to each other, either directly or indirectly. In this situation, neither object’s reference count ever drops to zero. Some solutions to the circular reference problem have been devised, but all add complexity and/or overhead.
Mark and SweepMark and sweep involves two phases. In the first phase, all objects in the heap are set to their unmarked state. Then, all objects directly or indirectly accessible from program variables are marked as “in-use.” In phase two, all of allocated memory is scanned (that is, a sweep of memory is made), and all unmarked elements are released.
There are two main advantages of mark and sweep. First, it easily handles circular references. Second, it adds virtually no runtime overhead prior to collection. It has two main disadvantages. First, a considerable amount of time might be spent collecting garbage because the entire heap must be scanned during collection. Thus, garbage collection may cause unacceptable runtime characteristics for some programs. Second, although mark and sweep is simple conceptually, it can be tricky to implement efficiently.
CopyingThe copying algorithm organizes free memory into two spaces. One is active (holding the current heap), and the other is idle. During garbage collection, in-use objects from the active space are identified and copied into the idle space. Then, the roles of the two spaces are reversed, with the idle space becoming active and the active space becoming idle. Copying offers the advantage of compacting the heap in the copy process. It has the disadvantage of allowing only half of free memory to be in use at any one time.
Which Algorithm?Given that there are advantages and disadvantages to all of the three classical approaches to garbage collection, it might seem hard to choose one over the other. However, given the constraints enumerated earlier, there is a clear choice: reference counting. First and most importantly, reference counting can be easily “layered onto” the existing C++ dynamic allocation system. Second, it can be implemented in a straightforward manner and in a way that does not affect preexisting code. Third, it does not require any specific organization or structuring of the heap, thus the standard allocation system provided by C++ is unaffected.
The one drawback to using reference counting is its difficulty in handling circular references. This isn’t an issue for many programs because intentional circular references are not all that common and can usually be avoided. (Even things that we call circular, such as a circular queue, don’t necessarily involve a circular pointer reference.) Of course, there are cases in which circular references are needed. It is also possible to create a circular reference without knowing you have done so, especially when working with third-party libraries. Therefore, the garbage collector must provide some means to gracefully handle a circular reference, should one exist.
To handle the circular reference problem, the garbage collector developed in this chapter will release any remaining allocated memory when the program exits. This ensures that objects involved in a circular reference will be freed and their destructors called. It is important to understand that normally there will be no allocated objects remaining at program termination. This mechanism is explicitly for those objects that can’t be released because of a circular reference. (You might want to experiment with other means of handling the circular reference problem. It presents an interesting challenge.)
Implementing the Garbage CollectorTo implement a reference counting garbage collector, there must be some way to keep track of the number of pointers that point to each piece of dynamically allocated memory. The trouble is that C++ has no built-in mechanism that enables one object to know when another object is pointing to it. Fortunately, there is a solution: you can create a new pointer type that supports garbage collection. This is the approach used by the garbage collector in this chapter.
To support garbage collection, the new pointer type must do three things. First, it must maintain a list of reference counts for active dynamically allocated objects. Second, it must keep track of all pointer operations, increasing an object’s reference count each time a pointer is pointed to that object and decreasing the count each time a pointer is redirected to another object. Third, it must recycle those objects whose reference counts drop to zero. Aside from supporting garbage collection, the new pointer type will look and feel just like a normal pointer. For example, all pointer operations, such as * and –>, are supported.
In addition to being a convenient way to implement a garbage collector, the creation of a garbage collection pointer type satisfies the constraint that the original C++ dynamic allocation system must be unaffected. When garbage collection is desired, garbage collection-enabled pointers are used. When garbage collection is not desired, normal C++ pointers are available. Thus, both types of pointers can be used within the same program.
To Multithread or Not?Another consideration when designing a garbage collector for C++ is whether it should be single-threaded or multithreaded. That is, should the garbage collector be designed as a background process, running in its own thread and collecting garbage as CPU time permits? Or, should the garbage collector run in the same thread as the process that uses it, collecting garbage when certain program conditions occur? Both approaches have advantages and disadvantages.
The main advantage to creating a multithreaded garbage collector is efficiency. Garbage can be collected when idle CPU cycles are available. The disadvantage is, of course, that C++ does not provide any built-in support for multithreading. This means that any multithreaded approach will depend upon operating system facilities to support the multitasking. This makes the code nonportable.
The main advantage to using a single-threaded garbage collector is portability. It can be used in situations that do not support multithreading or in cases in which the price of multithreading is too high. The main disadvantage is that the rest of the program stops when garbage collection takes place.
In this chapter, the single-threaded approach is used because it works in all C++ environments. Thus, it can be used by all readers of this book. However, for those readers wanting a multithreaded solution, one is given in Chapter 3, which deals with the techniques needed to successfully multithread a C++ program in a Windows environment.
When to Collect Garbage?One final question that needs to be answered before a garbage collector can be implemented: When is garbage collected? This is less of an issue for a multithreaded garbage collector, which can run continuously as a background task, collecting garbage whenever CPU cycles are available, than it is for a single-threaded garbage collector, such as that developed in this chapter, which must stop the rest of the program to collect garbage.
In the real world, garbage collection usually takes place only when there is sufficient reason to do so, such as the case of memory running low. This makes sense for two reasons. First, with some garbage collection algorithms, such as mark and sweep, there is no way to know that a piece of memory is unused without actually performing the collection. (That is, sometimes there is no way to know that there is garbage to be collected without actually collecting it!) Second, collecting garbage is a time-consuming process which should not be performed needlessly.
However, waiting for memory to run low before initiating garbage collection is not suitable for the purposes of this chapter because it makes it next to impossible to demonstrate the collector. Instead, the garbage collector developed in this chapter will collect garbage more frequently so that its actions can easily be observed. As the collector is coded, garbage is collected whenever a pointer goes out of scope. Of course, this behavior can easily be changed to fit your applications.
One last point: when using reference-count based garbage collection, it is technically possible to recycle unused memory as soon as its reference count drops to zero, rather than using a separate garbage collection phase. However, this approach adds overhead to every pointer operation. The method used by this chapter is to simply decrement the memory’s reference count each time a pointer to that memory is redirected and let the collection process handle the actual recycling of memory at more convenient times. This reduces the runtime overhead associated with pointer operations, which one typically wants to be as fast as possible.
Monday, June 18, 2007
4.5 Inline function
1. In general, each formal argument is replaced with its corresponding actual argument.
{
int minval = i
}
Using the MATLAB Engine
Some of the things you can do with the MATLAB engine are
Call a math routine, for example, to invert an array or to compute an FFT from your own program. When employed in this manner, MATLAB is a powerful and programmable mathematical subroutine library.
Build an entire system for a specific task, for example, radar signature analysis or gas chromatography, where the front end (GUI) is programmed in C and the back end (analysis) is programmed in MATLAB, thereby shortening development time.
The following topics provide information for using MATLAB engine:
The MATLAB engine operates by running in the background as a separate process from your own program. This offers several advantages:
On UNIX, the MATLAB engine can run on your machine, or on any other UNIX machine on your network, including machines of a different architecture. Thus you could implement a user interface on your workstation and perform the computations on a faster machine located elsewhere on your network. The description of the engOpen function offers further information.
Instead of requiring that all of MATLAB be linked to your program (a substantial amount of code), only a small engine communication library is needed.
The Engine Library
The engine library contains the following routines for controlling the MATLAB computation engine. Their names all begin with the three-letter prefix eng. These tables list all the available engine functions and their purposes.
C Engine Routines
Function | Purpose |
---|---|
Start up MATLAB engine | |
Shut down MATLAB engine | |
Get a MATLAB array from the MATLAB engine | |
Send a MATLAB array to the MATLAB engine | |
Execute a MATLAB command | |
Create a buffer to store MATLAB text output | |
Start a MATLAB engine session for single, nonshared use | |
Determine visibility of MATLAB engine session | |
Show or hide MATLAB engine session |
Fortran Engine Routines
Function | Purpose |
---|---|
Start up MATLAB engine | |
Shut down MATLAB engine | |
Get a MATLAB array from the MATLAB engine | |
Send a MATLAB array to the MATLAB engine | |
Execute a MATLAB command | |
The MATLAB engine also uses the mx prefixed API routines discussed in Creating C Language MEX-Files and Creating Fortran MEX-Files.
Communicating with MATLAB
On UNIX, the engine library communicates with the MATLAB engine using pipes, and, if needed, rsh for remote execution. On Microsoft Windows, the engine library communicates with MATLAB using a Component Object Model (COM) interface. COM and DDE Support (Windows Only), contains a detailed description of COM.
GUI-Intensive Applications
If you have graphical user interface (GUI) intensive applications that execute a lot of callbacks through the MATLAB engine, you should force these callbacks to be evaluated in the context of the base workspace. Use evalin to specify that the base workspace is to be used in evaluating the callback expression, as follows:
engEvalString(ep, "evalin('base', expression)")
Specifying the base workspace in this manner ensures MATLAB processes the callback correctly and returns results for that call.
This does not apply to computational applications that do not execute callbacks.
4.4 Pointer to Member Functions
The value returned from taking the address of a nonstatic member function, if it is nonvirtual, is the actual address in memory where the text of the function is located. This value, however,equally incomplete. It, too, needs to be bound to the address of a class object before an actual invocation of the member function is possible. The object's address serves as the this pointer argument required by all nonstatic member functions.
The syntax of declaring a pointer-to-member function is: double (Point::* pmf) ();
Example:
double (Point::* coord) = &Point::x; //Point is a class name
coord = &Point::y; //to assign a value to it
(origin.*coord)(); //Invocation uses the pointer-to-member selection operator
(ptr->*coord)();
Supporting pointer-to-virtual-member functions
Taking the address of a virtual member function yields its index into its class's associated virtual table. The compiler must distinguish the type of the two values: an address in memory (a large number) and an index into the virtual table.
cfront implementation:
((int)pmf & ~127)
? //non-virtual invocation
(*pmf)(ptr)
: //virtual invocation
( *ptr->vptr[(int)pmf]) (ptr);
this implementation assumes a limit of 128 virtual functions to an inheritance graph. This is not desirable, but in practice it has proved viable.
Pointer-to-Member Functions under MI
Stroustrup design an aggregate structure:
struct __mptr
{
int delta; // this pointer offset
int index: //virtual table index
union
{
ptrtofunc faddr; // nonvirtual member function address
int v_offset; //location of the vptr of a virtual base class
}
};
original implementation:
(ptr->*pmf)() will be transformed into:
(pmf.index < 0)
? // non-virtual invocation
(*pmf.faddr)(ptr)
: // virtual invocation
(*ptr->vptr[pmf.index])(ptr);
Microsoft, provide the following optimization:
1. A single inheritance instance (which simply holds either the vcall thunk or function address)
2. A multiple inheritance instance (which holds the faddr and delta members)
3. A vitual inheritance instance (which holds four members)
static class member
静态数据成员只有一份, 由该类类型的所有对象共享访问. 静态数据成员在该类定义之外被初始化.静态成员的名字必须被其类名限定修饰. 与全局对象一样, 静态成员在程序中也只能提供一个定义, 初始化不应该放在头文件中, 而是应该放在含有类的非inline函数定义的文件中.
作为特例, 整型的const静态数据成员可以在类体中用一常量初始化.
静态数据成员的特有属性:
1. 静态数据成员的类型可以是其所属类, 而非static的数据成员只能被声明为该类的对象的指针或者引用.
2. 静态数据成员可以被作为类成员函数的缺省实参, 而非static成员不能.
静态成员函数的声明除了在类体中的函数前加上关键字static, 以及不能声明为const或volatile. 出现在类体外的函数定义不能指定关键字static.
静态成员函数没有this指针.
const, volatile, mutable
不好的风格: 把一个成员函数声明为const可以保证这个成员函数不修改类的数据成员,但是如果该类含有指针, 那么const 成员函数中就能修改指针所指的对象,编译器不会把这种修改检测为错误.
const成员函数可以被相同参数表的非const成员函数重载, 在这种情况下,类对象的常量性决定了调用哪一个函数. 构造函数和析构函数是两个例外. 一个const类对象"从构造完成时刻到析构开始时刻" 这段时间被认为是const.
也可以将函数成员声明为volatile, 如果一个类对象的值可能被修改的方式是编译器无法控制的,则把它声明为volatile. 与const类对象类似, 对于一个volatile类对象, 只有volatile成员函数,构造和析构函数可以被调用.
mutable数据成员:
mutable数据成员永远不会是const成员,即使它是一个const类对象的数据成员. mutable成员总可以被更新,即使是在一个const成员函数中. 为了把一个成员声明为mutable数据成员,我们必须把关键字mutable放在类成员表中的数据成员声明之前.
Saturday, June 16, 2007
4.2 Virtual Member Functions
In C++, polymorphism "exhibits" itself as the potential addressing of a derived class object through a pointer or reference of a public base class. The presence of at least one virtual function is the criterion for identifying those classes requiring additional runtime information.
ptr -> z(); // z() is a virtual function
What information is needed to invoke the correct runtime instance of the virtual fucntion z()?
1. The actual type of the object addressed by ptr. This allow us to choose the correct instance of z().
2. The location of that instance of z() in order to invoke it.
In C++, the set of virtual functions capable of being invoked through an object of its class is known at compile time. Moreover, this set is invariant. It can NOT be added to nor can a virtual instance be replaced at runtime. The table, therefore, serves only as a passive repository.
An internally generated virtual table pointer is inserted within each class object to find the table. To find the function's address, each virtual function is assigned a fixed index within the table.
The virtual table is generated on a per-class basis. Each table holds the addresses of all the virtual function instances "active" for objects of the table's associated class. These active functions consist of the following:
1. An instance defined within the class, thus overriding a possible base class instance.
2. An instance inherited from the base class, should the derived class choose not to override it.
3. A pure_virtual_called() library instance that serves as both a place holder for a pure virtual function and a runtime exception should the instance somehow be invoked.
Each virtual function is assigned a fixed index in the virtual table. The index remains associated with the particular virtual function throughout the inheritance hierarchy. 每个slot对应的virtual function永远不变。
Three possibilities:
1. It can inherit the instance of the virtual function declared within the base class. Literally, the address of that instance is copied into the associated slot in the derived class's virtual table.
2. It can override the instance of one of its own. In this case, the address of its instance is placed within the associated slot.
3. It can introduce a new virtual function not present in the base class. In this case, the virtual table is grown by a slot and the address of the function is placed within that slot.
ptr->z() will be transformed into (*ptr->vptr[4])(ptr); The only thing we don't know until runtime is the address of which instance of z() is actually contained in slot 4.
Virtual Functions under Multiple Inheritance (难点)
The complexity of virtual function support under multiple inheritance revolves around the second and subsequent base classes and the need to adjust the this pointer at runtime.
The general rule is that the this pointer adjustment of a derived class virtual function invocation through a poniter (or reference) of a second or subsequent base class must be accomplished at runtime. That is, the size of the necessary offset and the code to add it to the this pointer mush be tucked aways somewhere by the compiler. Where?
One efficient solution is the use of a thunk. The thunk is a small assembly stub that a) adjusts the this pointer with the appropriate offset and then b) jumps to the virtual function. In this way, the address placed within each slot either directly addresses the virtual function or addresses an associated think, if an adjustment of the this pointer is necessary.
Under multiple inheritance, a derived class contains n-1 additional virtual tables, where n represents the number of its immediate base classes. (Sun compiler concatenates the multiple virtual tables into one for speedup purpose)
Virtual Functions under Virtual Inheritance
The beginning of the derived class and base class is no longer coincident, so conversion between the two also requires a this pointer adjustment. Stanley recommends: Not to declare nonstatic data members within a virtual base class.
Friday, June 15, 2007
4.1 Varieties of Member Invocation
Nonstatic Member Functions
One C++ design criterion is that a nonstatic member function at a minimum must be as efficient as its analogous nonmumber function. This is achieved by internally transforming the member instance into the equivalent nonmember instance.
The steps in the transformation of a member function:
1. Rewrite the signature to insert an additional argument to the member function that provides access to the invoking class object. This is called the implicit this pointer.
2. Rewrite each direct access of a nonstatic data member of the class to access the member through the this pointer.
3. Rewrite the member function into an external function, mangling its name so that it's lexically unique.
Name Mangling
In general, member names are made unique by concatenating the name of the member with that of the class. Function names are made unique by internally encoding the signature types (argument lists are referred to as the function signature) and concatenating those to the name.
class Point
{
public:
void x__5Point(float newX);
float x__5Point();
}
class Point
{
public:
void x__5PointFf(float newX);
float x__5PointFv();
}
By encoding the argument list with the function name, the compiler achieves a limited form of type checking across separately compiled modules. For example, if a print function is defined as
void print (const Point3d &) {...}
but was accidentally declared and invoked as
void print (const Point3d);
the unique name mangling of the two instances would cause any invocation of the incorrect instance to fail to be resolved at link time - sometimes optimistically referred to as type-safe linkage. I say "optimistically" because the process catches function signature errors only; a misdeclared return type still goes unnoticed.
Virtual Member Functions
If normalize() were a virtual member function, the call
ptr->normalize();
would be internally transformed into
(*ptr->vptr[1])(ptr);
the second ptr represents the this pointer.
The explicit invocation of a virtual function using the class scope operator is resolved in the same way as a nonstatic member function.
//explicit invocation suppresses virtual mechanism
register float mag = Point3d::magnitude();
is transformed internally into:
register float mag = magnitude__7Point3dFv(this);
Objects do not support polymorphism. The invocation of a virtual function through a class object should always be resolved by your compiler as an ordinary nonstatic member function.
Static Member Functions
The primary characteristic of a static member function is that it is without a this pointer. The following secondary characteristics all derive from that primary one:
1. It can NOT directly access the nonstatic members of its class
2. It can NOT declared const, volatile, or virtual
3. It does NOT need to be invoked through an object of its class, although for convenience, it may.
Taking the address of a static member function always yields the value of its location in memory, that is, its address. The type of its address value is not a pointer to class member function but the type of a nonmember pointer to function.
3.6 Pointer to Data Members
& class_name::data_member is going to yield the offset within the class object. each actual member offset is bumped up by 1. to distinguish between a pointer to no data member and a pointer to the first data member.
float Point3d::*p1 = 0;
float Point3d::*p2 = &Point3d::x;
//oops: how to distinguish?
if ( p1 == p2) {...}
& object_name.data_member yields the membejr's actual address in memory. The result adds the offset of data member (minus 1) to the beginning address of the object. The value returned is of type float * because it refers to an specific single instance, much the same as taking the address of a static data member.
Thursday, June 14, 2007
Virtual Memory
1.Unallocated. Unallocated blocks do not have any data associated with them, and thus do not occupy any space on disk.
2.Cached
3.Uncached
3.4 Inheritance and the Data Member
Inheritance without Polymorphism (virtual functions)
What are the possible pitfalls of transforming two independent classes into a type/subtype relationship through inheritance?
1. A naive design might, in face, double the number of function calls to perform the same operations. Choosing candidate functions for inlining is an important, if unglamorous, aspect of class design.
2. A second possible pitfall in factoring a class into a two-level or deeper hierarchy is a possible bloating of the space necessary to represent the abstraction as a class hierarchy. The issue is the language guarantee of the integrity of the base class subobject within the derived class.
Adding Polymorphism (The heart of Object Oriented Programming)
* Introduction of a vritual table associated with the class to hold the address of each virtual function it declares. The size of this table in general is the number of virtual functions declared plus an additional one or two slots to support runtime type identification.
* Introduction of the vptr within each class object. The vptr provides the runtime link for an object to efficiently find its associated virtual table. vptr will also be inherited by the derived class.
* Augmentation of the constructor to initialize the object's vptr to the virtual table of the class. Depending on the aggressive of the compiler's optimization, this may mean resetting the vptr within the derived and each base class constructor.
* Augmentation of the destructor to reset the vptr to the associated virtual table of the class.(The oder of destructor calls is in reverse: derived class and then base class)
Placing the vptr at the start of the class is more efficient in supporting some virtual function invocations through pointers to class members under multiple inheritance.
Multiple Inheritance
For single inheritance, the base and derived class objects both begin at the same address. They differ in that the derived object extends the length of its nonstatic data members.
Placing the vptr at the beginning of the class object breaks the natural polymorphism of single inheritance in the special case of a base class without virtual functions and a derived class with them. The conversion of the derived object to the base in this case requires the intervention of the compiler in order to adjust the address being assigned by the size of the vptr. Under both multiple and virtual inheritances, the need for compiler intervention is considerably more prnounced.
The problem of multiple inheritance primarily affects conversions between the derived and second or subsequent base class objects.
What about access of a data member of a second or subsequent base class? Is there an additional cost?
No. The member's location is fixed at compile time. Hence its access is a simple offset the same as under single inheritance regardless of whether it is a pointer.
Multiple inheritance will inherit multiple vptrs.
Virtual Inheritance
A semantic side effect of multiple inheritance is the need to support a form of shared subobject inheritance. The language support level solution is the introduction of virtual inheritance.
The general implementation solution is as follows. A class containing one or more virtual base class subobjects, is divided into two regions: an invariant region and a shared region. Data within the invariant region remains at a fixed offset from the start of the object regardless of subsequent derivations. So members within the invariant region can be accessed directly. The shared region represents the virtual base class subobjects. The location of data within the shared region fluctuates with each derivation. So members within the shared region need to be accessed indirectly. What has varied among implementations is the method of indirect access.
The general layout sttrategy is to first lay down the invariant region of the derived class and then build up the shared region. How is the implementation to gain access to the shared region of the class?
1. In the original cfront implementation, a pointer to each virtual base class is inserted within each derived class object.
2. Microsoft's compiler introduced the virtual base class table. Each class object with one or more virtual base classes has a pointer to the virtual base class table inserted within it. The actual base class pointers, of course, are placed within the table.
3. Sun's compiler, virtual table offset strategy, the virtual function table is indexed by both positive and negative indices. The positive indices, as previously, index into the set of virtual functions; the negative indices retrieve the virtual base class offsets.
Access of an inherited virtual base class member through a nonpolymorphic class object can be optimized by an implementation into a direct member access, much as a virtual function call through an object can be resolved at compile time.
In general, the most efficient use of a virtual base class is that of an abstract virtual base class with no associated data members.
Source Insight
Source Insight is a revolutionary project oriented program code editor and code browser, with built-in analysis for C/C++, C#, and Java programs, as well as other languages. Source Insight parses your source code and maintains its own database of symbolic information dynamically while you work, and presents useful contextual information to you automatically. Not only is Source Insight a great program editor, but it also can display reference trees, class inheritance diagrams, and call trees. Source Insight features the quickest navigation of source code and source information of any programming editor. Let Source Insight loose on your project and see what a difference it makes in your productivity. A free 30 day trial version is available here.
Source Insight was designed for large, demanding, real world programming projects. In fact, Source Insight is being used today to develop some of the largest and most successful commercial software products ever written.
Synchonization Variables
Mutex, Semaphore, Condition variable
Using Mutexes in the Different Libraries:
POSIX
pthread_mutex_lock(m)
...
pthread_mutex_unlock(m)
Win32
WaitForSingleObject(m)
...
ReleaseMutex(m)
Synchronization Issues
Critical Section||
--------------------
A critical section is a section of code that must be allowed to complete atomically with no interruption that affects its completion. We create critical sections by locking a lock, manipulating the data, then releasing the lock afterwards. The thread that is executing in the critical section may even lose its processor, but no other thread may enter the critical section.
---------------------------------------
Protecting Shared Resources
----------------------
Mutual Exclusion||
----------------------
Enter mutal exclusion. Mutual exclusion is the method of serializing access to shared resources. You do not want a thread to be modifying a variable that is already in the process of being modified by another thread! Another scenario would be a dirty read where the value is in the process of being updated and another thread reads an old value.
Mutual exclusion (most often referred to as mutex) allows the programmer to "attach" locks to resources. If a thread wishes to modify or read a value from a shared resource, the thread must first gain the lock. Once it has the lock it may do what it wants with the shared resource while it has the lock because no other thread should have access to that variable. Once the thread finishes using the shared resource, it unlocks the mutex, which allows other threads to access the resource.
The code between the lock and unlock calls to the mutex, is referred to as the critical section. Minimizing time spent in the critical section allows for greater concurrency because it reduces the time other threads must wait to gain the lock. Therefore, it is important for a thread programmer to minimize critical sections.
-----------------------------
Problems with Mutexes ||
-----------------------------
1. Deadlock
2. Race
3. Priority inversion
An important problem associated with mutexes is the possibility of deadlock. A program can deadlock if two (or more) threads have stopped execution or are spinning permanently. The simplest deadlock situation: thread 1 locks lock A, thread 2 locks lock B, thread 1 wants lock B and thread 2 wants lock A. Instant deadlock. You can prevent deadlock by making sure threads acquire locks in an agreed order (lock ordering). Deadlock can also happen if threads do not unlock mutexes properly.
Race conditions occur when multiple threads share data and at least one of the threads accesses the data without going through a defined synchronization mechanism (Nichols 203). This could result in erroneous results even in an inconsistent manner which makes race conditions particularly difficult to debug. Library calls outside of your program's control are common culprits. Make sure you take steps within your program to enforce serial access to shared file descriptors and other external resources.
Another problem with mutexes is that contention for a mutex can lead to priority inversion. A higher priority thread can wait behind a lower priority thread if the lower priority thread holds a lock for which the higher priority thread is waiting. This can be eliminated/reduced by limiting the number of shared mutexes between different priority threads.
----------------
Semaphores||
----------------
Semaphores are another type of synchronization primitive that come in two flavors: binary and counting. Binary semaphores act much like mutexes, while counting semaphores can behave as recursive mutexes. Counting semaphores can be initialized to any arbitrary value which should depend on how many resources you have available for that particular shared data. Many threads can obtain the lock simultaneously until the limit is reached. This is referred to as lock depth.
References:
1. Threads Primer
2. http://vergil.chemistry.gatech.edu/resources/programming/threads.html
Wednesday, June 13, 2007
3.3 Access of a Data member
Static data members are literally lifted out of their class and treated as if each were declared as a global variable (but with visibility limited to the scope of the class). This is the only case in the language where the access of a member through a pointer and through an object are exactly equivalent in terms of the instructions actually executed.
Taking the address of a static data member yields an ordinary pointer of its data type, NOT a pointer to class member, since the static member is not contained within a class object.
Name-mangling is used to yield a unique program identifier if two class declare the same static variable.
Nonstatic Data Members
Nonstatic Data members are stored directly within each class object and can NOT be accessed except through an explicit or implicit class object. An implicit class object (represented by the this pointer) is present whenever the programmer directly accesses a nonstatic data member within a member function.
The offset of each nonstatic data member is known at compile time, even if the member belongs to a base class subobject derived through a single or multiple inheritance chain. Access of a nonstatic data member, therefore, is equivalent in performance to that of a C struct member or the member of a nonderived class.
Virtual inheritance introduces an additional level of indirection in the access of its members through a base class subobject.
Point3d origin;
Point3d *pt;
origin.x = 0.0;
pt->x = 0.0;
acess of those two methods are different?
The answer is the access is significantly different when the Point3d class is a derived class containing a virtual base class within its inheritance hierarchy and the member being accessed, such as x, is an inherited member of that virtual base class. With the object origin, the offset location of even inherited virtual base class members are fixed at compile time. But we can't say with any certainty which class type pt addresses, so the resolution of the access must be delayed until runtime through an additional indirection.
3.2 Data Member Layout
The standard requires within an access section only that the members be set down such that "later members have higher addresses within a class object". That is, the members are not required to be set down contiguously. What might intervene between the declared members? Alignment constraints on the type of a succeeding member may require padding.
The compiler may synthesize one or more additional internal data members in support of the Object Model. The standard, by phrasing the layout requirement as it does, allows the compiler the freedom to insert theres internally generated members anywhere. Where should the vptr be placed within the class object? More recently, it has been placed at the beginning of the class object. Also in practice, multiple access sections are concatenated together into one contiguous block in the order of declaration. No overhead is incurred by the access section specifier or the number of access levels.
3.1 The binding of a Data Member
The body of an inline function is not evaluated until after the entire class declaration is seen.
typedef int length;
class Point3d
{
public:
mumble (length val) { _val = val;} // length resolves to global, _val resolves to Point3d::_val
private:
typedef float length;
length _val;
}
When the subsequent declaration of the nested typedef of length is encountered, the Standard requires that the earlier bindings be flagged as illegal. This aspect of the language still requires the general defensive programming style of always placing nested typedef declarations at the beginning of the class.
3.0 The Semantics of Data
class X{}; // size: 1
class Y : public virtual X {}; // size: 8
class Z : public virtual X {}; // size: 8
class A : public Y, public Z {}; // size: 12
class X {} in pratice is never empty. Rather it has an associated size of 1 byte - a char member inserted by the compiler. This allows two objects of the class to be allocated unique addresses in memory.
The class Y and Z size on any machine is the interplay of three factors:
1. Language support overhead. There is an associated overhead incurred in the language support of virtual base classes.
2. Compiler optimization or recognized special cases. There is the 1 byte size of the virtual base class X subobject also present within Y and Z (Potential optimiazation is desired).
3. Alignment constraits. On most machines, aggregate structures have an alignment constraint so that they can be efficiently loaded from and stored to memory.
The empty virtual base class has become a common idiom of OO design under C++. It provides a virtual interface without defining any data. Some recent compilers provide special handling and an empty virtual base class is treated as being coincident with the beginning of the derived class object.
A virtual base class subobject occurs only once in the derived class regardless of the number of times it occurs within the class inheritance hierarchy.
The C++ standard does not mandate details such as the ordering of either base class subobjects or of data members across access levels. Stanley distinguish between what the Standard mandates and what the current standard practice is.
Nonstatic data members hold the values of individual class objects; static data members hold values of interest to the class as a whole. Static data members are maintained within the global data segment of the program and do not affect the size of individual class objects.
The object size may at times surprise you as being larger than necessary in two ways:
1. Additional data members added by the compilation system to support some language functionality
2. Alignment requirements on the data members and data structures as a whole
Tuesday, June 12, 2007
2.4 Member Initialization List
You must use the member initialization list in the following cases in order for your program to compile:
1. When initializing a reference member
2. When initializing a const member
3. When invoking a base or member class constructor with a set of arguments
4.
The compiler iterates over the initialization list, inserting the initializations in the proper order within the constructor prior to any explicit user code. The order in which the list entries are set down is determined by the declaration order of the members within the class declaration, not the order within the initialization list.
This apparent anomaly between initialization order and order within the initialization list can lead to the following nasty pitfall:
class X
{
int i;
int j;
public:
X(int val):j(val), i(j) {}//Problem!
}
The author recommends always placing the initialization of one member with another (if you really feel it is necessary) within the body of the constructor.
2.2 Copy Constructor Construction
There are three program instances in which a class object is initialized with another object of its class.
1) an object's explicit initialization
class X {...};
X x;
X xx = x; //explicit initialization of one class object with another
2) an object is passed as an argument to a function
3) when a function returns a class object.
This may result in the generation of a temporary class object or the actual transformation of program code (or both).
Default memberwise Initialization
What if the class does not provide an explicit copy constructor?
default memberwise initialization copies the value of each built-in or derived data member from the one class object to another. A member class object, however, is not copied; rather, memberwise initialization is recursively applied.
In practice, a good compiler can generate bitwise copies for most class objects since they have bitwise copy semantics.
The standard states that:
A class object can be copied in two ways, by initialization and by assignment. Conceptually, these two operations are implemented by a copy constructor and a copy assignment operator.
The standard distinguishes between a trival and nontrivial copy constructor. It is only the nontrivial instance that in practice is synthesized within the program. The criteria for determining whether a copy constructor is trivial is whether the class exhibits bitwise copy semantics. A default copy constructor need not be synthesized, if the declaration exhibits bitwise copy semantics, and the initialization need not result in a function call.
There are four instances when bitwise copy senmantics NOT exhibited by a class:
1) When the class contains a member object of a class for which a copy constructor exists (either explicitly declared by the class designer or synthesized by the complier).
2) When the class is derived from a base class for which a copy constructor exists
3) When the class declares one or more virtual functions (we need to initialize vptr correctly)
4) When the class is derived from an inheritance chain in which one or more base classes are virtual.
Resetting the Virtual Table Pointer: When we assign derived class object to base class object, we need to set vptr to base class virtual table.
Handling the Virtual Base Class Subobject: The problem is not when one object of a class is initialized with a second object of the same exact class. It is when an object is initialized with an object of one of its derived classes.
2.1 Default Constructor Construction
Global objects are guaranteed to have their associated memory "zeroed out" at program start-up. Local objects allocated on the program stack and heap objects allocated on the free-store do not have their associated memory zeroed out; rather, the memory retains the arbitrary bit pattern of its previous use.
When is a default constructor synthesized, then? Only when the implementation needs it.
The standard states the following:
If there is no user-defined constructor for class X, a default constructor is implicitly declared... A constructor is trivial if it is an implicitly declared default constructor...
A nontrivial default constructor is one that is needed by the implementation and, if necessary, is synthesized by the compiler. There are four conditions under which the default constructor is nontrivial.
1) Member Class Object with Default Constructor
2) Base Class with Default Constructor
3) Class with a Virtual function
4) Class with a Virtual Base Class
1) Member Class Object with Default Constructor
If a class without any constructors contains a member object of a class with a default constructor, the implicit default constructor of the class is nontrivial and the compiler needs to synthesize a default constructor for the containing class.
class Foo { public: Foo(), Foo(int) ...};
class Bar { public: Foo foo; char *str;};
void foo_bar()
{
Bar bar; // bar::foo must be initialized here...
if ( str ) { } ...
}
The synthesized default constructor contains the code necessary to invoke the class Foo default constructor on the member object Bar::foo, but it does not generate any code to initialize Bar::str. Initialization of Bar::foo is the compiler's responsibility; initialization of Bar::str is the programmer's.
inline Bar::Bar()
{
//Pseudo C++ code
foo.Foo::Foo();
}
The sythesized default constructor meets only the needs of the implementation, not the needs of the program. If the programmer provides for the initialization of str via the following default constructor:
//Programmer defined default constructor
Bar::Bar () { str = 0;}
Because the default constructor is explicitly defined, the compiler cannot synthesize a second instance to do its work. In this case, the compiler augments the existing constructors, inserting code that invokes the necessary default constructors prior to the execution of the user code.
//Augment default constructor
Bar::Bar ()
{
foo.Foo::Foo(); //augmented compiler code
str = 0; //explicit user code
}
For multiple class members requiring constructor initialization, the compiler will insert code within each constructor, invoking the associated default constructors for each member in the order of member declaration. This code is inserted just prior to the explicitly supplied user code.
2) Base Class with Default Constructor
The synthesized default constructor of the derived class invokes the default constructor of each of its immediate base classes in the order of their declaration.
What if the designer provides multiple constructors but no default constructor? The compiler will augment each constructor with the code necessary to invoke all required default constructors.
3) Class with a Virtual function
There are two additional cases in which a synthesized default constructor is needed:
3.1 The calss either declares (or inherits) a virtual function
3.2 The class is derived from an inheritance chain in which one or more base classes are virtual.
The following two class "augmentations" occur during compilation:
1. A virtual function table is generated and populated with the addresses of the active virtual functions for that class. 如何判定一个方程是 active的呢?
2. Within each class object, an additional pointer member (the vptr) is synthesized to hold the address of the associated class vtbl.
widget.flip()
{
( * widget.vptr[1]) (&widget);
}
In classes that do not declare any constructors, the compiler synthesizes a default constructor in order to correctly initialize the vptr of each class object.
4) Class with a Virtual Base Class
We need to make the virtual class location within each derived class object available at runtime. All reference and pointer access of a virtual base class is achieved through the associated pointer. For each constructor the class defines, the compiler inserts code that permits runtime access of each virtual base class.
There are four characteristics of a class under which the compiler needs to synthesize a default constructor for classes that declare no constructor at all. The Standard refers to these as implicit, nontrivial default constructors. The synthesized constructor fulfills only an implementation need. It does this by invoking member object or base class default constructors or initializing the virtual function or virtual base class mechanism for each object. Classes that do not exhibit these characteristics and that declare no constructor at all are said to have implicit, trivial default constructors. In practice, these trivial default constructors are not synthesized.
Within the synthesized default constructor, only the base class subobjects and member class objects are initialized. All other nonstatic data members, such as integers, pointers to integers, arrays of integers, and so on, are not initialized. These initializations are needs of the program, not of the implementation. If there is a program need for a default constructor, such as initializing a pointer to 0, it is the programmer's responsibility to provide it in the course of the class implementation.
Programmers new to C++ often have two common misunderstandings:
-
That a default constructor is synthesized for every class that does not define one
-
That the compiler-synthesized default constructor provides explicit default initializers for each data member declared within the class
As you have seen, neither of these is true.
Dynamic Link Library
Stands for "Dynamic Link Library." A DLL (.dll) file contains a library of functions and other information that can be accessed by a Windows program. When a program is launched, links to the necessary .dll files are created. If a static link is created, the .dll files will be in use as long as the program is active. If a dynamic link is created, the .dll files will only be used when needed. Dynamic links help programs use resources, such as memory and hard drive space, more efficiently.
DLL files can also be used by more than one program. In fact, they can even be used by multiple programs at the same time. Some DLLs come with the Windows operating system while others are added when new programs are installed. You typically don't want to open a .dll file directly, since the program that uses it will automatically load it if needed. Though DLL filenames usally end in ".dll," they can also end in .exe, .drv, and .fon, just to make things more confusing.
Monday, June 11, 2007
Sunday, June 10, 2007
Introduction to Algorithms
The lecture videos are available on internet.
Enjoy~
library: .lib, .dll
Bridge Pattern
The implementation of the class has been moved to an implementation class hidden from general use.
class C
{
public:
C(int val);
~C();
int get_a() const;
int get_b() const;
private:
Cimpl *impl_;
};
class Cimpl
{
public:
Cimpl(int val) : a_(val),b_(val) {}
~Cimpl() {}
int get_a() const {return a_;}
int get_b() const {return b_;}
private:
int a_;
int b_;
};
C::C(int val): impl_(new Cimpl(val)) {}
C::~C() {delete impl_;}
int C::get_a() const
{
return impl_->get_a();
}
int C:: get_b() const
{
return impl_->get_b();
}
Employing a Bridge incurs a clear runtime cost, each member function call is both indirect and non-inline. The advantages lie in the ability to update client code without recompilation.
Applications: patch updates(bug fixes, typically) into installed software.
C++ Gotcha
http://www.semantics.org/
Chapter 1: Basics
Gotcha #1: Excessive Commenting
If meaningful and mnemonic names are used in a program, there is ofter only occasional need for additional comments. Follow a simple, well-defined naming convention and choose clear names that reflect the abstract meaning of the entity (function, class, variable, and so on). Formal argument names in declarations are particularly important. The goal is to employ the minimal volume of comments that permits the code to be readily understood and maintained.
Gotcha #2: Magic Numbers (Raw numeric literals)
Raw numeric literals off no advantage and many disadvantages. Use enumerators and initialized constants instead. Enumerators consume no space and cost nothing in runtime while providing clear, properly scoped names of the concepts for which they stand.
Gotcha #3: Global Variables
Avoid global variables. Global variables increase coupling among components, impede code resue and make code hard to maintain. If a function or class guards (One method is to employ Singleton pattern) access to the global information, the setting of the initial value can be delayed until it's safe to do so.
Gotcha #4: Failure to Distinguish Overloading from Default Initialization
Overloading is generally used to indicate that a set of functions has common abstract meaning but different implementations. Default initialization is generally used for convenience, to provide a simplified interface to a function.
Gotcha #5: Misunderstanding References
A reference is an alias for its initializer. The only thing one can do with a reference is to initialize it. After that, it's simply another way of referring to its initializer.A reference doesn't have an address, and it's even possible that it might not occupy any storage. It is illegal to attempt to declare a reference to a reference, a pointer to a reference, or an array of references. No null reference, no references to void. A reference return from a function allows assignment to the result of a call. For example, an index function for an abstract array. Reference may also be used to provide additional return values for functions.
Gotcha #6: Misunderstanding Const
int i = 12; const int *pi = &i; Here const means a restriction on how we may manipulate i through pi rather than on how i may be manipulated in general.
Gotcha #7: Ignorance of Base Language Subtleties
int ary[12]; int *p = &ary[5]; p[2]=7; 2[p]=7; p[2] is entirely equivalent to *(p+2). the addition is commutative and it's legal to index an integer with a pointer: 2[p]=6;
Gotcha #8: Failure to Distinguish Access and Visibility
One way to aviod recompilation is to "forward declare" the class by using an incomplete class declaration in contexts where more information about the class is not required: class C. Such an incomplete declaration will still allow us to declare pointers and references to a C as long as we perform no operations that require the knowledge of C's size or members, including base class subobjects. To avoid maintenance problems, it's important to pick up the incomplete class declaration from the same source that supplies the class definition. We should provide a "forward declaration" header file that supplies an appropriate set of forward declarations. Bridge pattern involves separating the class into two parts, an interface and an implementation.
Gotcha #9: Using Bad Language
There is no way to represent a null pointer directly in C++, but we're guaranteed that the numeric literal 0 is convertible to a null pointer value for any pointer type. real C++ programmers still use 0 to represent the null pointer value.
Gotcha #10: Ignorance of Idiom
class X
{
public:
X (const X &);
X & operater = (const X &);
}
copy operation idiom. copy constructor and copy assignment operator. Both operations take a reference to a constant, and the copy assignment is nonvirtual and returns a reference to a non-const.
The approach taken by the standard auto_ptr is to depart from the copy operation idiom intentionally:
template <>
class auto_ptr
{
public:
auto_ptr(auto_ptr &);
auto_ptr & operator = (auto_ptr &);
private:
T * object_;
};
Here the right side of each operation is non-const. When one auto_ptr is initialized by or assigned to by another, the source of the initialization or assignment gives up ownership of the heap-allocated object to which it refer by setting its internal object pointer to null.
Gotcha #11: Unnecessary Cleverness
It is nearly always preferable to be conventional, clear, and slightly less efficient than unnecessarily clever, unclear, and unmaintainable.
Gotcha #12: Adolescent Behavior
Chapter 2: Syntax
Gotcha #13: Array Initializer Confusion
In general, prefer vector to low-level arrays.
int *ip = new int[12]; // square brackets
std::vector <> iv (12); //parentheses
Gotcha #14: Evaluation Order Indecision
Function argument evaluation is not fixed to a particular order. It's best to minimize side effects in function arguments.
Gotcha #15: Precedence Problems
Most C++ operators are left-associative, and C++ has no nonassociative operators.
int a = 3, b = 2, c = 1;
if(a>b>c) //legal, but probably wrong... the result is false
Gotcha #16: for Statement Debacle
It is generally a good idea to restrict the scope for a variable to the region of the program in which it is used. It is possible to declare a variable in the condition of an if-statement. The variable will be available in the statements controlled by the condtion, both the "true" and "false" branches. The same is true of the for-statement. Under the new semantics, the scope is restricted to the for-statement itself. All for-statements be written to the new semantics is recommended..
Gotcha #17: Maximal Munch Problems
a+++++b is illegal because it's tokenized as a ++ ++ + b, and it's illegal to post-increment an rvalue like a++. In one of the early stages of C++ translation, the portion of the compiler that perfroms "lexical analysis" has the task of breaking up the input stream into individual "words," or tokens. To avoid this ambiguous state of affairs, the lexical analyzer always identifies the longest token possible, consuming as many characters as it legally can: maximal munch.