Generic: transfer constructor

zhaozj2021-02-16  60

Wide : Transfer Construction Function

Andrei alexandrescu

I think you know very clearly, create, copy, and destroy the temporary object is your C compiler love, the temporary object is created inside, unfortunately this greatly affects the execution efficiency of the C program. In fact, temporary objects are the biggest influence factors in the C program.

Such a code looks good:

Vector ReadFile ();

Vector vec = readfile ();

or:

STRING S1, S2, S3;

S1 = S2 S3;

If you need efficiency, you don't use this code. ReadFile () and Operator created temporary objects are copied to the target object, and then dropped - how waste!

In order to solve this problem, you need to follow less beautiful norms. For example, you should transfer the target object as a parameter of the function.

Void ReadFile (Vector & Dest);

Vector DEST;

Readfile (DEST);

This is really troublesome. Worse, the operator function does not give you this option, so if you want to process your big object, you must limit the operator that you don't have to build a temporary object:

STRING S1, S2, S3;

S1 = S2;

S1 = S3;

These clumsy common methods often spread in large processes in large project groups, constantly bring trouble, affect the fun of writing code, and increase the number of code lines. If we can return values ​​from the function, use operators, and free delivery of temporary objects, but don't have to worry about the time of a lot of time to create / copy / destroy the action, how good.

is it possible?

In fact, this is not "this better" this dream. The entire C community requires solving this extra copy issue. There are extensive interest in this issue. There is already a formal draft that has been submitted to the Standard Commission [2]. The draft uses a language-based solution. This discussion is everywhere in UseNet, and this article you are reading now has been discussed warmly.

This article tells you how to solve this unnecessary copy issue existing in C . There is no 100% suitable solution, but it can achieve a large extent. We will establish a complete powerful framework step by step to help you eliminate excess copy temporary objects in your program. This solution is not 100% transparent, but it does drop all unnecessary copies, and provides sufficient packaging as a reliable alternative, until a few years, a clearer, language-based approach is standardized And implemented.

Temporary object and "Move Constructor"

After fighting with the temporary object, people realize that the removal of true temporary objects are not true in most cases. Many times, the problem is to eliminate unnecessary temporary object copies. Please let me explain it in detail.

Most "expensive copies" data structures store their data in the form of pointers or handles. A typical example is: A String type stores a size and a char *, a Matrix type stores an integer and a double *, or a File type stores a handle.

As you can see, copy String, Matrix or File does not come to copy the actual data member, but from the data referenced by the replication being pointed or handle.

Knowing these, our goal is to eliminate copies, a good way is to check a temporary object. Anyway, the object is destroyed, we can use it when it can use it.

But how can it be a temporary object? Let's propose a controversial definition: In an environment, only the destructor is only a function that only the destructor is only a function of an object to exit the environment, this object is considered temporary

This environment can be an expression or a domain (such as a function)

The C standard does not define a temporary object, but it thinks the temporary object is anonymous (such as a function return value). Use our (more generalized) definition, named stack variable defined in the function is also a temporary object, and we will use this idea to bring this idea.

Consider this ordinary String class implementation

Class String

{

Char * DATA_;

SIZE_T Length_;

PUBLIC:

~ String ()

{

DELETE [] DATA_;

}

String (Const String & RHS)

: Data_ (New Char [rhs.length_]), Length_ (rhs.length_)

{

Std :: Copy (rhs.data_, rhs.data_ length_, data_);

}

String & Operator = (const string);

.

}

The overhead of copying here is mainly to copy Data_, which is allocated for new memory and copy it. If we can know that RHS is actually a temporary object. Consider the following C pseudo code:

Class String

{

...

String (Temporary String & RHS)

: DATA_ (RHS.DATA_), Length_ (rhs.length_)

{

// Reset the source string so that it can be destroyed

// Don't forget that the destructor is still performed on the temporary object.

RHS.DATA_ = 0;

}

......

}

This imaginary overload constructor String (Temporary String &) works when you construct a string from a String of temporary objects (Return values ​​called by a function). The constructor is then transferred to the created object by simply copying the pointer (with the memory block pointed to which it points to) is not copied. Final but very important, transfer constructor reset source pointer rhs.data_. Thus, when the temporary object is destroyed, delete [] will be harmlessly acting on an empty pointer.

An important detail is that RHS.LENGTH_ is not set to zero after the transfer structure. This is theoretical to be incorrect (we get an invalid String, its DATA == 0, Length _! = 0), but doing this has a good reason. The final state of RHS does not have to be consistent. This is because the only operation to be executed on RHS is a destructor - no other. So as long as RHS meets the conditions that can be destroyed, it is not necessary to be considered a legitimate string.

The transfer constructor is a good way to eliminate unnecessary copy temporary objects. We only have a small problem - there is no Temporary key in C language.

(It should be noted that checking the temporary object is not useful for all classes. Sometimes all data is stored directly into the container, such as:

Class fixedmatrix

{

Double Data_ [256] [256]

PUBLIC:

... Operation function ...

}

For such a class, it is really copying all sizeof (fixedmatrix) bytes is an expensive operation, and check the temporary object is not used.)

Previous method

Unnecessary copy is the old problem in the C community. There are two solution directions, one starts from the write code / library direction, and the other starts from the language definition / compiler direction.

From the language / compiler point of view, we have "return value optimization," abbreviated as RVO. RVO is C language definition [3] clearly allowed. Basically, for all functions, your C compiler can confirm if it can do RVO. This function is a copy constructor, and the premise is that the compiler thinks the copy constructor makes a copy.

It is because the compiler thinks that it can remove unnecessary copies. such as:

Vector ReadFile ()

{

Vector Result;

... Fill Result ...

Return Result;

}

Vector vec = readfile ();

A smart compiler can be passed into the VEC's address as a hidden parameter of the ReadFile and create a Result on that address. So the code generated from the above source code will like this:

Void readfile (void * __dest)

{

// Use Placement New on the address DEST

// Create a vector

Vector & Result =

* New (__ dest) vector ;

... Fill Result ...

}

// Suppose it is correctly aligned

Char __buf [sizeof (Vector )]]

Readfile;

Vector & Vec =

Reinterpret_cast *> (__ buf);

RVO has several different forms, but the point is the same, the compiler eliminates the call of the copy constructor by simply constructing the function return value on the final target object.

Unfortunately, the implementation of RVO is not like it looks so easy. Suppose ReadFile makes a small change:

Vector ReadFile ()

{

IF (error) return vector ();

IF (Anothererror)

{

Vector Dumb;

Dumb.push_back ("this file is in error.");

Return DUMB;

}

Vector Result;

... Fill Result ...

}

Now not a local variable needs to be mapped to the final result, but three. Some are named (Result), some are unknown temporary objects. Needless to say, in the face of such a situation, many optimizers will give up, turn the way to use conservative and low efficiency.

Even if you are ready to write "obvious" code, you will be disappointed by each compiler, and often each compiler version is often detected and implemented by RVO. Some RVOs are used only when the function returns the function (the simplest form of RVO). More complex compilers use RVO (so-called naming RVO, or NRVO) when the function is naming.

Basically, when writing code, you can think that RVO can be used universally in your code depends on how you correctly write code (this "correct" definition is very uncertain), the moon's profit, and your toe size.

But wait, there is worse. It is often the compiler even if you want to do RVO, but still can't do it. Imagine this slight change to readfile () calls:

Vector Vec;

Vec = readfile ();

Although this change looks harmless, the situation has changed huge changes. We now call the assignment operator function instead of the copy constructor, this is a different big monster. Unless you optimize your compiler, you can kiss your RVO now: Vector :: Operator = (Const Vector &) Requires a Vector's const reference, so readfile () The returned temporary object is bound to the Const reference, copy to VEC, and then discarded. Unnecessary temporary object objects appear again! From the point of view of the encoding, there is a technology that has been recommended for a long time is COW (Copy-on-Write Write Copy) [4], which is a technique based on reference.

COW has several advantages, one is it checks and eliminates unnecessary copies. For example, when a function returns, the reference number of the return object is 1. Then you copy it, this increases its reference records to 1. Finally, you destroy the temporary object, the reference number is returned to 1. At this point, the target object is the only owner of the data. Did not do the actual copy.

Unfortunately, the reference count has many shortcomings at the same time: it brings its own overhead, and many hidden problems [4]. COW is too difficult to use, so that although it exists, the current STL implementation is not in std :: string, although the actually std :: string interface is originally designed to support the reference number! .

A number of "non-replication" objects have been developed, where auto_ptr is the best one. Auto_PTR is easy to use correctly, but unfortunately, it is also easy to be misused. The methods discussed herein extend the techniques used in Auto_PTR.

Mojo

Mojo (Move Of Joint Objects Joint Object) is an encoding technology and a small frame, which is to eliminate unnecessary temporary object copies. Mojo works by identifying temporary objects and legal "non-temporary" objects.

Maximum parameter to function

First make an interesting analysis, review the rules of the pass parameters to the function. Mojo's general recommendation is like this.

1. If the function needs to change the parameters as an attached effect, the parameter is a reference / pointer to a non-Const object, for example:

Void Transmogrify (Widget & Tochange);

Void Increment (int * ptobump);

2. If the function does not change the parameters and the parameters are the basic type, they will pass the values. E.g:

Double Cube (Double Value);

3, in addition, the parameter is (or possibly when you define a template) a user-defined type and cannot be changed, so pass through the Const reference, for example:

String & string :: Operator = (const string & rhs);

Template Vector :: Push_Back (const t &);

The purpose of the third rule is to avoid accidental copying large objects. However, sometimes this is caused by rules that prevent unnecessary copy! Suppose you have a function like the CONNECT:

Void Canonicalize (String & URL);

Void Resolveredirections (String & URL);

Void Connect (Const String & URL)

{

String firmurl = URL;

Canonicalize (FinalURL); Resolveredirections (FinalURL);

... Use FinalURL ...

}

Conncet takes a reference to Const as a parameter and immediately creates a copy of its copy. The function then further operates this copy.

This function demonstrates how Const is an obstacle to efficiency. Connect's statement says: "I don't need a copy, a reference to Const is enough" - but the function entity actually established a copy. So if you write now:

String Makeurl ();

Connect (Makeurl ());

Then you can think that Makeurl () returns a temporary object, it is copied and destroyed: the terrible unnecessary copy mode. For a compiler to optimize this copy, the arduous job that must be done is {1} to enter the defined section of Connect (different compilation modules will bring difficulties), {2} Analyze the definition of Connect, establish a definition of function Understand, then {3} change the behavior of Connect, such temporary objects and FinalURL be integrated.

Suppose you change the connect:

Void Connect (String URL) // Note, Passage Call

{

Canonicalize (URL);

Resolveredirections (URL);

... Use FinalURL ...

}

From the perspective of conncet's caller, there is no difference, although you change the syntax interface, the semantic interface is the same. For the compiler, this syntax change has a completely different results. Now the compiler has more room for handling the URL temporary object. For example, in the above example:

Connect (Makeurl ());

The compiler is not difficult to combine the temporary objects returned by Makeurl into the temporary object required for Connect. In fact, it is really difficult to do this! In the end, the last result of Makeurl will be changed and used in Connect. The previous version allows the compiler very depressed and prevents it from performing any optimization. This version is smoothly working with the compiler.

The disadvantage of this new setting is that the CONNECT may now produce more machine code. such as:

String SomeURL = ...

Connect (Someurl);

In this example, a version of the head will simply pass a SomeURL reference. The second version creates a copy of someURL, calls Connect, and destroy the copy. The overhead of this code size increases with the increase of the number of Connect fixed calls. From another aspect, the call to the temporary object can be generated, such as Connect (MakeURL ()) can generate less code in the second version. At least, the difference between code size is unlikely to be a problem.

So we have a different recommendation table:

3.1. If the function always makes a copy of its parameters inside, pass the value to it.

3.2. If the function does not copy its parameters, pass the const reference to it.

3.3. If the function is sometimes copying the parameters and is important to you, follow the principle of Mojo

Anyway, the only thing left now is to define "Mojo Principles",

Basic ideas is to overload the same function (such as Connect), with the aim of distinguishing between temporary and non-closing value. (Due to historical reasons, the latter is also considered "LVALUES": White, the left value can be placed on the left side of the assignment operation)

When starting overloading Connect, a possible idea is to define Connect (const string &) to capture the "real" constant object. However, this will be an error, because this statement will "eat" all String objects - whether the left value is still a temporary value. So the first good idea is not to declare a function that accepts the const reference, because it swallowed all objects like a black hole. The second attempt is to define Connect (String &), with the purpose to capture all non-Const left values. This is good, especially the const value and the unknown temporary object will not be "eaten" in this overload function - a nice opening. Now we only need to distinguish const objects and non-Const temporary objects.

To achieve the purpose, the means we use is to define two "type assist" CONSTANTSTRING and TEMPORARYSTRING, and define the conversion operators from String to these objects:

Class string;

// "Type-assisted" for constant String

Struct constantstring

{

Const string * obj_;

}

// "Type-assisted" for temporary String

// (explained behind)

Struct TemporaryString: Public constantstring {};

Class String

{

PUBLIC:

... Constructor, destructor,

Other functions, you decided ...

Operator constantstring () constant

{

ConstantString Result;

Result.Obj_ = this;

Return Result;

}

Operator temporarystring ()

{

TemporaryString Result;

Result.Obj_ = this;

Return Result;

}

}

This now String defines two conversion operators. One of them is significantly different from the TempoRingString function does not work on the const string object.

Now if you define the following three overload functions:

/ / Bind to a non-Const temporary object

Void Connect (TemporaryString);

// Bind to all const objects (left value and non-left value)

Void Connect (constantstring);

// Bind to non-const left values

Void Connect (String & Str)

{

/ / Only go to another overload function

Connect (constantString (str));

}

Now said the working principle. Constant String objects are "attracted" by Connect (constantstring), without other binding work; the other two only works for non-Const String.

Temporary objects cannot be passed to Connect (String &). However, they are able to pass Connect (TemporaryString) or Connect (constantString), and the previous overload must be selected without selection. This is why TemporaryString is inherited from constantstring, which is a payable technique.

Think about it, if constantstring and temporaryString are completely unrelated types, then when copying a temporary object, the compiler has the same reason to call any one:

Operator temporaryy () à y (Temporanyy)

or:

Operator constanty () const à y (constanty)

Why do you have the same reason? Because the object is switched from const to non-Const is "free friction" when selecting a member function.

So, what needs to be done is to give the compiler more "reason" to select the first instead of the second. This is exactly inherited. Now the compiler says: "Well, I can use constantstring or temporarystring, but wait, inherited TemporaryString is a better match!" The rule here is when selecting one from a bunch of overload functions, match inheritance ratio Matching base classes are more suitable. [Translation 1]

Finally, an interesting change is - inheritance does not have to be public. Access rights rules and overload rules do not interfere.

Let's see how Connect is used for an instance:

String sl ("http://moderncppdesign.com");

// Call Connect (String &)

Conncet (S1);

// After calling Operator TemporaryString ()

// Call Connect (TemporaryString)

Connect (String ("http://moderncppdesign.com");

Const String S4 ("http://moderncppdesign.com"));

// Call the operator constantString () after Const

// Call Connect (constantString)

Connect (S4);

As you can see, we have achieved the main goals of the desired, and we distinguish between the temporary objects and all other objects, this is the main point of Mojo.

There are not very important aspects, most of them will talk about. First, there is a small code repeat: Connect (String &) and Connect (constantString) must actually do the same thing. The above code solves this problem by transferring the second overload function from the first overload function.

Second, let us face this, in order to write two small classes to each type of Mojo, we can't make these code more common to improve its availability. We defined a namespace Mojo, and we put two generic CONSTANT and TEMPORARY:

Namespace Mojo

{

Template

Class Constant // Auxiliary type for constant objects

{

Const T * DATA_;

PUBLIC:

Explicit Constant (Const T & Obj): Data_ (& Obj)

{

}

Const T & Get () Const

{

Return * Data_;

}

}

Template

// Auxiliary type for temporary objects

Class Temporary: Private Constant

{

PUBLIC:

Explicit Temporary (T & Obj): Constant (OBJ)

{

}

T & Get () const

{

Return const_cast (constant :: get ());

}

}

}

We simultaneously define a base class mojo :: enabled, which defines two operations:

Template struct enabled // In Mojo Namespace

{

Operator Temporary ()

{

Return Temporary (static_cast (* this));

}

Operator constant {} constant

{

Return Constant (static_cast (* this));}

protected:

Enabled () {} // In order to be inherited

~ enabled () {} // In order to be inherited

}

With this framework, "Mojo" a class of tasks makes more simpler:

Class String: Public Mojo :: Enabled

{

... constructor, destructor, other functions you define ...

PUBLIC:

String (Mojo :: Temporary TMP)

{

String & rhs = tmp.get ();

... Perform destructive copy to * this ...

}

}

This is the MOJO rule that passes the parameters to the function.

Sometimes, all things have been working well, you have designed an excellent work that has not been used yet, in fact, this kind of good momentum will be further strengthened, and this makes all this more valuable.

This is the case, we can easily know if a class supports Mojo, as long as you write:

Namespace Mojo

{

Template

Struct Traits

{

ENUM {enabled =

Loki :: supersubclassstrictcture , t> :: value};

}

}

Loki has a ready-made mechanism that checks if a type is from another inheritance [5].

Now you can check if any type X is designed to support Mojo by using Mojo :: Traits :: enabled. This check mechanism is very important in generic code, we will soon see this.

Optimization function return value

Now there is no problem, let's see how to extend the MOJO expand into a function return value. Once again, the goal is universal optimization - 100% elimination unnecessary copy, does not depend on any specific RVO implementation.

We first look at the general suggestions. For good reasons, some authors also recommend the return value of ConSt [7]. Continue the old rules:

4. When a function returns a user-defined value, returns a const value. E.g:

Const String Operator (Const String & LHS,

Const string & rhs)

The idea of ​​rule 4 is that by prohibiting the wrong expression, for example, it is necessary to write to IF (S1 S2 == S3) but write into IF (S1 S2 = S3) to make user-defined operation behavior and within The construction is very similar. If Operator returns a const value, this particular error is discovered when compiling. However, other author [6] is recommended not to return the const value.

From a philosophical point of view, a return value is a lot of flowers, and it is a rapid disappearance. So why do you force Operator customers to accept a constant value? Where is this butterfly "often"? From this perspective, const and temporary values ​​look contradict each other. In practice, Const temporary values ​​enforce copies to destination objects.

Suppose we agree that if the efficiency is very important, it is best not to add const on the return value. How do we let the compiler transfer the function result to the destination object instead of copy it?

When copying an object of Type T, the copy constructor is called. The copy constructor is a function of a different and other functions. It seems that we can use the above method to draw the following frame settings:

Class String: PUBLIC MOJO :: Enabled {

......

PUBLIC:

String (string &);

String (mojo :: temporary );

String (Mojo :: constant );

}

This is a very good setting, except for a small detail - it can't work.

I remember when I said: "The copy constructor is a function like any other function"? Ok, I am lying. The copy constructor is a special function, and it is annoying. If a corresponding type x you define x (x &) instead of x (const X ", then the following code will not run:

Void FunctionTakingX (Const X &);

FunctionTakingTakingX (x ()); // Error!

// Unable to find X (Const X &)

This greatly weakens X, so we must have String (const string &) constructor. Now please allow me to quote this article, in somewhere, I said: "The first good idea is not to declare a function that accepts the const reference, because it is swallowing all objects like a black hole."

Can you say this is "self-contradictory"?

Obviously, the copy constructor needs to be taken differently. The solution here is to create a new class, FnResult, which is "porter" as a String object. Here is the steps we need to do:

1. Define fnResult, let a function that returns T now returns FnResult . In order to make this change to the caller, FnResult must be replaced with T.

2. Establish the transfer semantic rule of FnResult, and the FnResult object is copied, and the T is transferred.

3, similar to Operator Constant and Temporary, providing a conversion operator for converting to FnResult in class mojo :: enabled

4. A Mojo chemical class (such as the string in our example) defines a constructor string (Mojo :: fnResult ) to complete the transfer

FnResult is defined as follows:

Namespace Mojo

{

Template

Class FnResult: Public T

{

PUBLIC:

, // If no one will really create one

// const fnResult object,

// The following conversion (CAST) is legal

FnResult (Const FnResult & RHS)

: T (Temporary (const_cast (rhs)))))

{

}

Explicit FnResult (T & RHS): T (Temporary

{

}

}

}

Because FnResult is inherited from T, step 1, switch from one FnResult to a T. Then, when a FnResult object is copied by force to transfer to Temporary to transfer its sub-object T, so step 2 is completed.

As mentioned earlier, we add a conversion operator that returns FnResult in enabled. The final version of enabled is as follows:

Template struct enabled

{

Operator Temporary () {

Return Temporary (static_cast (* this));

}

Operator constant () const

{

Return Constant (static_cast (* this));

}

Operator FnResult ()

{

Return FnResult (static_cast (* this));

}

protected:

Enabled () {} // In order to be inherited

~ enabled () {} // In order to be inherited

}

Finally, String defines the constructor mentioned in step 4. Below is a string with all constructor:

Class String: Public Mojo :: Enabled

{

......

PUBLIC:

// Copy rhs

String (const string & rhs);

// Transfer tmp.get () to * this

String (Mojo :: Temporary TMP);

// Transfer RES to * THIS

String (Mojo :: FnResult res);

}

Now consider the following functions:

Mojo :: fnResult MakeString ()

{

String result;

? ...

Return Result;

}

...

String dest (makeString ());

From MakeString Return statements to DEST's path is: resultàstring :: Operator FnResult () àfnResult &) àstring :: string (fnResult )

A compiler using RVO can eliminate FnResult & Cable. But the most important thing is that there is no function to perform a true copy, which is defined as able to smoothly transferring the content in the result to DEST. There is no memory copy without memory allocation.

Now as you can see, there are two, up to three transfer operations. It is possible to have a copy than three transfer for some type and a copy ratio (speed). But there is an important difference: copy may fail (throw an accident), but the transfer will never.

expansion

Ok, we let Mojo start working, and you work very well on a separate class, now to expand MOJO, support the composite objects containing many other objects, some "other objects" is also Mojo.

This task is from a transfer constructor to "down" to its member. For example, implant the String class above a Widget class:

Class Widget: PUBLIC MOJO :: Enabled

{

String name_;

PUBLIC:

Widget (Mojo :: Temporary SRC) // Source is a temporary object

: Name_ (Mojo :: as_temporary (src.get (). name_))

{

Widget & rhs = src.get ();

... Use RHS to perform destruction copy ...

}

Widget (Mojo :: FnResult src) // Source is a function returns: name_ (Mojo :: as_temporary (src.name_))

{

Widget & rhs = SRC;

... Use RHS to perform destruction copy ...

}

}

The destruction of the copy function is used to use an important MOJO assisted function to NAME_:

Namespace Mojo

{

Template

Struct Traits

{

ENUM {enabled =

Loki :: supersubclassstrictcture , t> :: value};

Typedef Typename

Loki :: SELECT <

Enabled,

Temporary ,

T &> :: result

Temporary;

}

Template

Inline TypeName Traits :: Temporary As_Temporary (T & SRC)

{

Typedef Typename Traits :: Temporary Temp;

Return Temp (SRC);

}

}

All as_temporary do is created from a left value to create a temporary object. Thus, the transfer constructor of the Member_ is called to create a target object.

If String is Mojo, Widget will benefit from it, if not, a direct copy will be executed. In other words, if string is a subclass of Mojo :: enabled , as_temporary returns a mojo :: temporary . Otherwise, as_temporary (String & SRC) is a constant function that accepts a String & parameter returns the same String & constant.

We have benefited from another Loki feature: Select :: result is t or u depends on the Boolean condition is true or false [5].

Application: AUTO_PTR's table brothers and Mojo containers

Assume that there is a Mojo_PTR class that prohibits them by applying some constructor:

Class Mojo_ptr: Public Mojo :: Enable

{

Mojo_ptr

PUBLIC:

// Source is a temporary object

Mojo_ptr (Mojo :: Temporary SRC)

{

MOJO_PTR & RHS = src.get ();

... Use RHS to perform destruction copy ...

}

// The source is a function returned

Mojo_ptr (Mojo :: FnResult SRC)

{

MOJO_PTR & RHS = src.get ();

... Use RHS to perform destruction copy ...

}

? ...

}

This class has an interesting behavior. You can't copy this class of Const objects. You can't copy the left value of this class! But you can copy (with transfer semantics) temporary objects, and you can explicitly transfer one object to the other by writing this way.

MOJO_PTR PTR1;

Mojo_ptr ptr2 = mojo :: as_temporary (PTR1);

This is not a big problem, Auto_PTR should also turn auto_ptr (auto_ptr &) to do so. Interesting place is actually not Mojo_ptr, but how to construct a high-efficiency container by using as_temporary, you can store the "typical" type, normal MOJO type, and similar to the MOJO_PTR type. Everything you need to do this is to use as_temporary when you need to move the element any time. For the "Typical" type, as_temporary is the constant function function. For MOJO_PTR, As_TemPorary is a function that provides a smooth transfer function. The Move and Uninitialized_Move template function (see the code) are also very easy to use. From the standard terminology, Mojo_Ptr is neither "copied copyable" nor "can assign values ​​assignable". However, Mojo_PTR can be seen as part of a new type of type, this new type of type is called "Transfer Move". This is an important new type, it should also include lock, file, and other handles that cannot be copied.

result? If you have hoped a "unique container" similar to a safe, clear semantic vector >, then you have received a special feature. At the same time, MOJO's VECTOR is a good time, such as Vector >, is also working well.

in conclusion

Mojo is a technology and a simple frame, its use is to eliminate unnecessary temporary object copies. Mojo introduces them to an overloaded function different from the left value by checking the temporary object. In this way, the function of accepting the temporary object can be sure that there is no other code to use the temporary object, thereby disrupting it.

MoJO is suitable for the rules of the customer code to follow a series of simple functional parameters and return values.

Mojo defines a special mechanism to eliminate the copy of the function returns.

Additional mechanisms and tedious types of operation make Mojo not 100% transparent to users, but as a library-based solution, its integration capacity is very good. As a strong alternative, Mojo is the best choice in a variety of applications until there is a better, language-based characteristics are set as standard and implemented.

Thank you

Mojo was seriously checked and experienced a short, but the initial stage of enriched.

David Abrahams has a prominent contribution to the transfer constructor. Rani Sharoni pointed out some subtle problems. Peter Dimov pointed out a key issue early in the design, which made Mojo start from the head.

Gary Powell makes Mojo to inherit a lot of work, and EVENY KARPOV uses template functions to greatly simplify the code. I hope we can discuss these improvements in the next article.

Thanks to Howard Hinnant, Peter Dimov, and Dave Abrahams proposes a proposal to join the transfer constructor.

A lot of enthusiastic volunteers around the world comments on this article. Thank you all! I want to point out that Walter E. Brown, David Brownell, Marshall Cline, Peter Dimov, Mirek Fidler, Daniel Frey, Dirk Gerrits, Fredrik Hedman, Craig Henderson, Howard Hinnant, Kevin S. Van Horn, Robin Hu, Grzegorz Jakacki, . Sorin Jianu, Jozef Kosoru, Rich Liebling, Ray Lischner, Eric Niebler, Gary Powell, William Roeder, Maciej Sinilo, Dan Small, Alf P. Steinbach, Tommy Svensson, David Vandevoorde, Ivan Vecerina, Gerhard Wesp, and Yujie Wu Postscript: Mojo is becoming more and more

Since Mojo published, or more accurately, it has been widely concerned since the Internet. The C community is undoubted to find how to remove the privileges, which is very good.

I received countless letters with suggestions and improvements. Most of the use of MOJO has been convenient, such as adding Operator-> for Mojo :: Temporary, or otherwise improved.

Dave Abrahams sent me the code correcting a typing error and an exception security issue in Mojo :: Uninitialized_move (you know the work of DAVE, so you can bet him is the first person who corrects unusual security.). Mojo :: uninitialized_move is like this:

Template

Iter2 uninitialized_move (iter1 begin, iter1 end, iter2 dest)

{

For (; begin! = end; Begin, DEST)

{

NEW (* DEST) T (As_TEMPORY (* Begin));

}

Return DEST;

}

First, New (* DEST) must be changed to New (& * Dest). Second, there is an exceptionally safe issue. If the T constructor throws an exception, the program creates some objects that it cannot track, which makes the program to enter a very bad state.

The correct version sent by DAVE is:

Template

Iter2 uninitialized_move (iter1 begin, iter1 end, iter2 dest)

{

TypedEf TypeName Std :: item2> :: value_type t;

ITer2 Built = DEST;

Try

{

FOR (; begin! = end; begin, built)

{

NEW (& * Built) T (AS_TEMPORY (* Begin));

}

}

Catch (...)

{

For (; dest! = built; DEST)

{

DEST-> ~ T ();

}

Throw;

}

Return Built;

}

As you can see, the modified code is to track the creation of the object by copying DEST to a new iterator built. Then use the Built in the entire creation object cycle. If any accident occurs, uninitialized_move is beautifully cleaned before exiting, so that the function either successfully creates all objects, either fail, what objects are not created. The subtle problem in a new implementation makes it no longer supporting ITER2 as an output iterator (Output Iterators). There is no doubt that output iterators do not allow you to keep its copy (iTer2 Built = DEST;) and use them later - you must finish everything.

If you want to think, this requirement is actually reasonable. The output iterator is like a word written in the paper, like the network packet flows to the line, or the moving note like the singer. You can't cancel these actions. The iteration made by the old version is "I will forget". The new version is done more carefully, if an error occurs, cancel any action. As a necessary price, new and more careful versions do not support output iterators. If you are like me, you will definitely like these, whether the theory or practice can explain the situation.

Thank Dave!

Finally, my long-term writer and the rising master Rani Sharoni (he made Loki compatible with Microsoft Visual C . Net) to me said Mojo's first implementation [8]: (This version is relatively simple but Peter Dimov found Many issues) may actually be correct. The following is related links [9, 10]. We can see how things develop, just like a smart prosecutor found new in a famous lawsuit, no one wants evidence. The letter written in Rani is like this:

First of all, I agree with you (even though I think I am not a master).

In fact, I am a little disappointed because some related figures (such as Steve Adamczyk) did not return to my post, but in 291 auto_ptr's comments, I really believe that your original implementation is legal according to the current standard, just like some auto_ptr Tips.

Anyway, the good news is Mojo to be used and if it satisfies its design, it is also very good. In addition, in addition to the factors mentioned above Sharoni, Mojo looks 100% to eliminate the most compact framework that unnecessary copies. Many people began using Mojo. You may have to see the report and performance data of Mojo's wide range of applications. By the way, if you have this data, as long as it is valid, it is sent.

Reference book and annotation

[1] Dov Bulka and David Mayhew. Efficient C : Performance Programming Techniques, (Addison-Wesley, 1999).

. [2] Howard E. Hinnant, Peter Dimov, and Dave Abrahams "A Proposal to Add Move Semantics Support to the C Language," ISO / IEC JTC1 / SC22 / WG21 - C , document number N1377 = 02-0035, September 2002 , .

[3] "Programming Languages ​​- C ," International Standard ISO / IEC 14882, Section 12.2.

[4] Herb Sutter. More Exceptional C (Addison-Wesley, 2002). [5] Andrei Alexandrescu. Modern C Design (Addison-Wesley, 2001).

[6] John Lakos. Large-Scale C Software Design (Addison-Wesley, 1996), Section 9.1.9.

[7] HERB SUTTER. EXCEPTIONAL C (Addison-Wesley, 2000).

[8]

[9] UseNet Posting by Rani Sharoni, .

[10] Standard C Defect Report, .

[Translation 1] Careful readers may notice if two conversion functions are incremented by Const ultimately affecting the selection of overload functions. That is to say, const and inheritance simultaneously determine which overload function called. As for how it is determined, it is a very complex calling process. Fun is Andrei students I seeing did not know before discussing the following principles what: http:? //Groups.google.com/groups hl = en & lr = & ie = UTF-8 & safe = off & frame = right & th = 959852371d21285f & seekm = au2ihh% 243rps5% 241% 40ID-14036.NEWS.DFNCIS.DE # link1, unfortunately, most domestic readers can not access Google Group (if you find a proxy server to go to Google Group, please tell me 10 million to write a letter tell me, thank you The main rule is such that the assumption is:

Class a {};

Class B: Public a {};

Void f (a);

Void f (b);

Class c {

PUBLIC:

Operator a () const {return a ();

Operator b () {Return b ();

}

Void foo ()

{

C C;

f (c); // (1)

}

The compiler has two options: f (a) and f (b), how to call F (b) is obvious, how to call F (a) is a bit complicated by càb conversion. There are two conversion paths càc constàa and càbàa, the first comparison does not include the path to the user-defined conversion, that is, càc const and càc, obviously the latter is better, so call f (a) requires càbàa, because càb is Its subsequences, so select f (b). If b is not a subclass or removal of Operator a (), or on Operator B (), the compiler gives an error in the call. We are easy to inquire.

Source code download

FTP: //ftp.cuj.com/pub/2003/2102/alexandr.zip

转载请注明原文地址:https://www.9cbs.com/read-17606.html

New Post(0)