Deep Exploration of C + + object model the 6th chapter of semantic study in implementation period

Source: Internet
Author: User
Tags object model static class value of pi

(i) Construction and destruction of objects (object construction and destruction)
In general, we will place the object as far as possible near the section of the program that uses it, which can save unnecessary objects from generating operations and destroying operations.

Global Objects
If we have the following program fragments:

Matrix identitymain(){    //identity 必须在此处被初始化    Matrix m1=identity;    ...    return0;}

C + + guarantees that the identity must be constructed before the identity is first used in the main () function, and the identity is destroyed before the main () function ends. So-called global object such as identity if there are constructor and destructor, we say it requires static initialization and memory release operations.
All global objects in a C + + program are set in the program's data segment. If the display specifies a value for it, this object will be the initial value. Otherwise, the memory content that object is configured to is 0.
In C, a global object can only be set as an initial value by a constant expression (which can be evaluated at compile time). Of course, constructor is not a constant expression. Although class object can be placed in data segment at compile time and the content is 0, constructor is not implemented until the program starts (startup). A "initialization expression for an object placed in program data segment" must be evaluated (evaluate), which is why an object requires static initialization.

local static variables (local statics Objects)
Let's say we have the following program fragments:

const Matrix&identity(){    static Matrix mat_identity;    //...    return mat_identity;}

What kind of semantics does local static class object guarantee?
1, Mat_identity constructor must only be performed once, although the above function may be called multiple times.
2, Mat_identity destructor must only be performed once, although the above function may be called multiple times.

One of the compiler's policies is to construct an object unconditionally at the start of the program (startup). This, however, causes all local static class objects to be initialized at the beginning of the program, even if the function they are in has never been called. Therefore, it is a good practice to construct the mat_identity only when the identity () is called. The following are the specific practices:
First, import a temporary object to protect the initialization of the mat_identity. When the identity () is processed for the first time, the temporary object is evaluated to false, so constructor is called and the temporary object is changed to True. This solves the problem of construction. At the opposite end, the destructor also needs to be conditionally executed on whether the mat_identity is constructed, very simply, if the temporary variable is true, the structure is good. The difficulty is that because Cfront generates C code, mat_identity is still local to the function, so there is no way to access it in a static memory release function.

The new rule requires the static local class objects in the compilation unit to be destroyed--in reverse order of construction. Since these objects are constructed when they are needed (for example, each function containing the static local class objects is first entered), the compile time cannot anticipate its collection and order. In order to support the new rule, it may be necessary to maintain a list of execution periods for the generated static class object.

an array of objects (array of Objects)
Suppose we have the following array definitions:

Pointkonts[10];

What needs to be done? If point does not define a constructor and does not define a destructor, then our work is no more than building an array of "built-in (build-in)" types, That is, we just need to configure enough memory to store 10 contiguous point elements.

However, point does define a default destructor, so this destructor must be performed on each element in turn. This is generally achieved through one or more runtime library functions. In C_front, we use a function named Vec_new () that produces an array constructed of class objects. Comparing the new compilers, including Borland, Microsoft, and Sun, is provided with two functions, one for the "No virtual base class" class, and the other for the "embedded virtual base class" class. The latter function is often referred to as vec_vnew (). The function types are usually as follows (of course there may be a slight difference on each platform):

void*vec_new(    void *array,             //数组起始地址    size_t elem_size,        //每一个class object的大小    int elem_count,          //数组中元素个数    (*constructor)(void*),    (*destructor)(void*,char))

The constructor and destructor parameters are function pointers for this class's default constructor and default destructor. The parameter array holds an address that is not a named array, which is 0. If it is 0, then the array will be dynamically placed in the heap via the application's new operator. Sun's handling of the "named array composed of Class objects" and "Dynamically configured arrays" is divided into two library functions: _vector_new2 and _vector_con, each with a virtual base class function Instance.
The parameter elem_size represents the number of elements in the array. In Vec_new (), constructor is performed on Elem_count elements. For compilers that support exception handling, the provision of destructor is necessary. Here is the vec_new () call operation that the compiler may have made for our 10 point elements:

Point konts[10];vec_new(&knots,sizeof(Point),10,&Point::Point,0);

If point also defines a destructor, the destructor must also be performed on the 10 point elements when Konts's life is over. I don't think you would be surprised that this is done by a similar vec_delete () (or a vec_vdelete (), if classes has virtual base classes), and its function type is as follows:

void*vec_delete(    void *array,             //数组起始地址    size_t elem_size,        //每一个class object的大小    int elem_count,          //数组中的元素个数    void (*destructor)(void*,char))

Some compilers add additional parameters to pass other values in order to conditionally guide the logic of Vec_delete (). In Vec_delete (), destructor is performed on Elem_count elements.
If the programmer provides one or more obvious initial values to an array of class objects, like this, how to:

Point konts[10]={    Point(),    Point(1.0,1.0,0.5),    -1.0};

Vec_new () is no longer necessary for those elements that have an obvious initial value. For those elements that have not yet been initialized, Vec_new () is performed as if it were an array of class elements, and the array is not explicit initialization list. So the previous definition is likely to be converted to:

 Pointkonts[Ten];//C+ + code//pre-initialization with explicitly initialized3An element Point:: Point(&konts[0]); Point:: Point(&konts[1],1.0,1.0,0.5); Point:: Point(&konts[2],-1.0,0.0,0.0);//After initializing the Vec_new7Elements of Vec_new (&knots+3, sizeof ( Point),7,& Point:: Point,0);

Default constructor and Arrays
If you want to remove a constructor address from the program, it is not possible. Of course, this is what the compiler should do when it supports vec_new (). However, by starting constructor with a pointer, you cannot (not be allowed) access the default argument values. The method used by Cfront is to produce an internal stub constructor with no parameters. The programmer-supplied constructor is called within its function, and the default parameter value is specified in the past (since constructor's address has been obtained, it cannot be an inline).

(ii) New and delete operators
The use of operator new appears to be a single operation, like this:

int *pi=newint(5);

But in fact it is done in two steps:
1. Configure the required memory with an appropriate instance of the new operator function:

//调用函数库中的new运算符int *pi=_new(sizeof(int));

2. Set the initial value of the configured object:

*pi=5;

Further, the initialization operation should be performed after the memory configuration succeeds (via the new operator):

//new 运算符的两个分离步骤//given:int*piint(5);//重写声明int*pi;if(pi=_new(sizeof(int)))    *pi=5;   //译注:成功了才初始化

The delete operator is similar in the case. When the programmer writes:

delete pi;

If the value of pi is 0,c++ the language will require the delete operator to have no action. The compiler must therefore construct a layer of protection for this call:

if(pi!=0)    _delete(pi);

Note that pi is not automatically cleared to 0, so follow-up behavior like this:

//没有良好的定义,但是合法if(pi && *pi==5)...

Although there is no good definition, it may (or may not) be evaluated as true. This is because the pi points to the stored changes or re-use, which may (or may not) occur.

The life of the object that Pi refers to will end with the delete. Therefore, any subsequent reference operation to the PI no longer guarantees good behavior and is therefore considered a bad procedural style. However, the continuation of Pi as a pointer is still possible (although its use is limited), for example:

//ok:pi仍然指向合法空间//甚至即使存储于其中的object已经不再合法if(pi==sentine1)...

Here, using the pointer Pi, and using the object referred to by PI, the difference is that the life is over. Although the object on this address is no longer legal, the address itself still represents a legitimate program space. So PI can continue to be used, but only in restricted cases, much like a void* pointer.

Use constructor to configure a class object, the case type. For example:

Point3d *origin=new Point3d;

is converted to:

Point3d *origin;//C++伪码if(origin = _new(sizeof(Point3d)))    origin=Point3d::Point3d(origin);

If you implement exception handing, the conversion result can be more complex:

//C++伪码if(origin = _new(sizeof(Point3d))){    try    {        origin=Point3d::Point3d(origin);    }    catch(...)    {        libraryfunction以        //释放new而配置的内存        _delete(origin);        //将原来的exception上传        throw;    }}

Here, if you configure object with the new operator and its constructor throws a exception, the configured memory is freed. Then exception is thrown out (uploaded).

The application of destructor is very similar. The following equation:

delete origin;

will become:

if(origin != 0){    //C++伪码    Point3d::~Point3d(origin);    _delete(origin);}

If in the case of exception handling, destructor should be placed in a try section. Exception handler calls the delete operator and then throws the exception again.
The General Library's implementation of the new operator is straightforward, but there are two ingenious things to consider (note that the following versions do not take into account exception handling):

externvoid*operatornew(size_t size){    if(size==0)        size=1;    void *last_alloc;    while(!(last_alloc=malloc(size)))    {        if(_new_handler)            (*_new_handler)();        else            return0;    }    return last_alloc;}

Although it is legal to write:

new T[0];

But the language requires that every call to new must return a unique pointer. The traditional way to solve this problem is to return a pointer to a memory area that defaults to 1-bytes (This is why the size in the program code is set to 1). Another interesting thing about this implementation technique is that it allows the user to provide a _new_handler () function that belongs to them. This is why every cycle calls _new_handler ().

The new operator is actually always done with the standard C malloc (), although there is no provision for this to be done. In the same case, the delete operator is always done with the standard C free ():

externvoidoperatordelete(void *ptr){    if(ptr)        free((char*)ptr);}

new semantics for arrays
When we don't write this:

int *p_array=newint[5];

, Vec_new () is not really called because its main function is to execute the default constructor on every element of the array composed of class objects. The new operator function is called:

int*p_array=(int*) _new(5*sizeof(int));

The same situation if we write:

//struct simple_aggr{float f1,f2;};simple_aggr *p_aggr=new simple_aggr[5];

Vec_new () will not be called. Why? SIMPLE_AGGR does not define a constructor or destructor, so configuring an array and clearing the operations of the P_AGGR array simply gets the memory and frees the memory. These operations are more than sufficient to be done by the new and delete operators.
However, if class defines a default constructor, some versions of Vec_new () are called to configure and construct an array of class objects. For example, this equation:

Point3d *p_array=new Point3d[10];

It is usually compiled as:

Point3d *p_array;p_array=vec_new(0,sizeof(Point3d),10,&Point3d::Point3d,&Point3d::~Point3d);

During the construction of an individual array element, if a exception,destructor occurs, it is passed to Vec_new (). Only the elements that have been properly constructed need to be destructor, because their memory is already configured, and Vec_new () is responsible for releasing those memory at the time of the exception.

The search for the array dimension, which has a great impact on the efficiency of the delete operator, leads to the compromise that the compiler looks for the dimensions of the array only when the brackets appear, otherwise it assumes that only a single object is to be deleted. If the programmer does not provide the necessary brackets, then only the first element will be refactored. Other elements still exist-although their associated memory has been asked to be returned.

The execution of the destructor on the array, as we can see, is based on the "destructor of the removed pointer type" given to the Vec_delete () function. This is obviously not what we want. In addition, the size of each element is also passed in the past. This is how Vec_delete () iterates through each element of the array.
Basically, programmers have to iterate through the entire array and implement the delete operator on each element. In this way, the call operation will be virtual.

the semantics of Placement Operator new
There is a pre-defined overloaded (overloaded) new operator, called placement operator new. It requires a second parameter, and the type is void*. The method is called as follows:

Point2w *ptw = new(arena) Point2w;

Where arena points to a chunk in memory that is used to place the newly generated point2w object. This pre-defined placement operator new implementation is surprisingly mundane. It simply passes the address referred to by the obtained pointer (ARENA) back:

voidoperatornew(size_t,void* p){    return p;}

If it only returns its second argument, what value does it have? In other words, why not simply write it this way:

Point2w *ptw=(Point2w*)arena;

In fact, this is just half of what happens. The other half cannot be generated by the programmer. Think about these questions:
1. What is the extension of the other half of the placement new operator capable of running effectively (and is not provided by the "Arena explicit operation (explicit Assignment)")?
2. What is the true type of arena pointer? What does this type imply?
The other half of Placement new operator is the automatic implementation of POINT2W constructor on the address referred to by Arena:

//C++伪码*ptw=(Point2w*) arena;if!=0)    ptw->Point2w::Point2w();

That is why placement operator new power is so powerful. This code determines where the objects is placed, and the compilation system guarantees that the constructor of the object will be implemented on it.

However, there is a slight bad behavior. Can you get it out? The following is a problematic program fragment:

//让arena成为全局性定义void fooBar(){    Point2w *p2w=new(arena) Point2w;    //...do it...    //...now manipulate a new object...    p2w=new(arena) Point2w;}

If placement operator constructs a new object on an object that already exists, and the existing object has a destructor, the destructor is not called. One way to call the destructor is to delete the pointer. But in this case, if you do something like this, it's definitely a mistake:

//以下并不是实施destructor的正确方法deletenew(arena) Point2w;

Yes, the delete operator will work, which is exactly what we expect. But it also releases the memory that is referred to by p2w, which is not what we want, because the next instruction is going to use p2w. Therefore, we should display the destructor and reserve the storage space for reuse:

//施行destructor的正确方法p2w->=new(arena) Point2w;

The only remaining problem is a design problem: the first call to placement operator in our example constructs the new object above the existing object? Or will it be structured on a new address? In other words, if we write this:

Point2w *p2w = new (arena) Point2w;

How do we know if the area referred to by arena needs to be deconstructed first? The question is not answered at the linguistic level. A reasonable custom is to take responsibility for the destructor of the new end.

Another issue concerns the true pointer type shown by Arena. C + + standard says it must point to the same type of class, or a "fresh" memory, enough to accommodate an object of that type. Note that the derived class is obviously not in the supported list. For a derived class, or other type that is not associated, its behavior is not illegal, but is undefined.

The "Fresh" storage space can be configured like this:

charnewchar[sizeof(Point2w)];

An object of the same type can be obtained as follows:

Point2w *arena = new Point2w;

In either case, the new point2w storage space does cover the arena location, and this behavior is under good control. However, generally speaking, placement new operator does not support polymorphism. The pointer to new should properly point to a pre-configured memory. If the derived class is larger than its base class, for example:

Point2w *p2w = new (arena) Point3w;

The constructor of POINT3W will lead to serious damage.

(iii) temporary objects (temporary Objects)
The destruction of a temporary object should be the last step in the evaluation of a complete expression. The complete expression causes the temporary object to be created.
If a temporary object is bound to a reference, the object remains until the end of the life of the initialized reference, or until the end of the temporary object's life category (scope)-depending on which case arrives first.

Reference: "Deep Exploration of C + + object Model"

Deep Exploration of C + + object model the 6th chapter of semantic study in implementation period

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.