[CUJ] Wide Programming - Transfer Construction Function

zhaozj2021-02-16  57

Topic: Wide Programming - Transfer Construction Function (Generic Programming: Move Constructor)

Author: Andrei Alexandrescu

Compilation: Dead Cat

Category: wang tianxing

original:

http://www.cuj.com/experts/2102/alexandr.htm

Summary:

This article describes how to use template techniques to eliminate unnecessary temporary object replication in C . In addition, this article uses a lot of technically noticed technically, even if you are not interested in eliminating the copy of the temporary object, this article is also worth reading.

Keywords: Temporary object template

1 Introduction

I believe everyone knows, creating, copying, and destroying temporary objects is the favorite indoor movement of the C compiler. Unfortunately, these behaviors reduce the performance of the C program. Indeed, temporary objects are usually considered to be the first factor inefficient of C program [1].

The following code is correct:

Vector ReadFile ();

Vector vec = readfile ();

or

STRING S1, S2, S3;

// ...

S1 = S2 S3;

However, if you care, you need to limit the use of similar code. The temporary objects created by readfile () and Operator are copied and then discarded. This is a waste!

In order to solve this problem, some less elegant agreements are required. For example, you can transfer function parameters according to the reference:

Void ReadFile (Vector & Dest);

Vector DEST;

Readfile (DEST);

This is quite annoying. Worse, the operator does not have this option, so if you want to efficiently handle your big object, the programmer must limit the use of the operator of the creating a temporary object:

STRING S1, S2, S3;

// ...

S1 = S2;

S1 = S3;

This hard-wrapped technique typically slows down the work efficiency of the large team of large-scale procedures. This strongly continuous troubles kills the fun of writing code and increases the number of code. You can return "value" from the function, you can use the operator, and you can handle the temporary variable, and there is no time to waste in this freely created / copy / destroy. Yeah!

A formal language-based solution proposal has been submitted to the Standardization Commission [2]. Usenet has already triggered a big discussion, and this article is therefore repeatedly discussed.

This article shows how to solve unnecessary replication issues of C existence. No 100% satisfied solution, but a clean level can be achieved. Let's create a powerful framework step by step to help us eliminate the copy of the temporary object from the program. This solution is not 100% transparent, but it eliminates all unwanted replication and is packaged after packaging, until many years later, a clean, language-based standardization implementation appears.

2 Temporary objects and "Move Constructors" (MOVE CONSTRUCTOR)

After a period of struggle with the temporary object, we realized that in most cases, it is unrealistic to completely eliminate the temporary object. Most of the time, the key is to eliminate the copy of the temporary object rather than the temporary object itself. Let's discuss this problem in detail below.

Most data structures with expensive replication overhead store their data in the form of a pointer or handle. Typical examples include strings (STRING) type storage size (size) and character pointer (char *), matrix type store a set of integer dimension and data storage area pointer (Double *), file type Save a file handle (Handle). As you can see, the overhead of the reproduction string, matrix or file is not from copying actual data members, but from the data of the pointer or handle pointing.

Therefore, for the purpose of eliminating the copy, the detection temporary object is a good way. The cruel point is that since an object is dead, we can use it fresh and use it as an organ donor.

By the way, what is a temporary object? Here is an informal definition:

When only the operation performed on the object is only a description function, an object is considered temporary when it is a destructive function when leaving a context. Here, the context may be an expression or a scope of a statement, such as a function.

The C standard does not define a temporary object, but it assumes that the temporary object is anonymous, such as the return value of the function. According to our more general definition, the variables assigned in the name defined in the function are also temporary. Later, in order to facilitate our use of this generalization definition.

Consider this String class's implementation (only as an example):

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 cost of replication here is mainly composed of Data_'s replication, that is, allocating new memory and copying. If you can detect that RHS is actually temporary. Consider the following C pseudo code:

Class String

{

//...Cit...

String (Temporary String & RHS)

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

{

// Reset source string make it destroyed

/ / Because the destructive function of the temporary object is still required

RHS.DATA_ = 0;

}

// ...

}

This fictional overload constructor string (Temporary String &) is called when creating a String temporary object (according to the previous definition). Then, this constructor performs a construction process of a RHS object transfer, just a simple replication pointer instead of the memory block pointing to the pointer. Finally, "Transfer Construction Function" reset source pointer rhs.data_ (returns to empty pointer). Using this method, when the temporary object is destroyed, delete [] will be harmless to the empty pointer [Translation note: C guarantees that the empty pointer is safe].

An important detail is "transfer construct" after rhs.length_ is not cleared 0. According to the point of view of the dogmatic, this is incorrect because Data _ == 0 and Length _! = 0, the string is destroyed. However, there is a reason for a very standing foot, because the status of RHS is not necessary, as long as it can be safe and correct destruction. This is because the only one of the RHS will be applied is the destructor, not other. So as long as RHS can be safely destroyed, do not look at whether it is like a legitimate string.

"Transfer Construction Function" is a good solution for eliminating unwanted temporary object replication. We only have a small problem, there is no Temporary keyword in the C language. It should also be noted that the probe of the temporary object will not help all classes. Sometimes all data is stored directly in the container. consider:

Class fixedmatrix

{

Double Data_ [256] [256]

PUBLIC:

//...operating...

}

For such a class, the replication cost is in terms of byte copy sizeof (FixedMatrix) byte, and the temporary object does not help [Demo] Because the array is not a pointer, it cannot be exchanged directly.

3 past solutions

Unnecessary replication is a long-term problem of C community. There are two effort to enter the head, one is from the perspective of the code and library, the other is the language definition and the compiler write level.

Language / compiler viewpoint, Return Value Optimization, RVO). RVO is defined by the C language [3] [Tetament], but is not mandatory, but implemented. Basically, the compiler assumes that the return value is replicated by a copy constructor.

Specifically, based on such assumptions, the compiler can eliminate unnecessary replication. For example, consider:

Vector ReadFile ()

{

Vector Result;

// ... Fill Result ...

Return Result;

}

Vector vec = readfile ();

The smart compiler can pass the address of the VEC as a hidden parameter to the ReadFile and create the Result on that address. So the above source code generated code looks like this:

Void readfile (void * __dest)

{

// Create a vector in the DEST address using Placement New

Vector & Result =

* New (__ dest) vector ;

// ... Fill Result ...

}

/ / Assume that there is a suitable byte alignment

Char __buf [sizeof (Vector )];

Readfile;

Vector & Vec =

* ReinterPret_cast *> (__ buf);

RVO has different styles, but is to be the same: the compiler eliminates the call of a copy constructor, and constructs the function return value by simply constructing the function on the final destination.

Unfortunately, the implementation of RVO is not as easy as it looks like. Consider the version of ReadFile a slightly modified version:

Vector ReadFile ()

{

IF (error) return vector ();

IF (Anothererror)

{

Vector Dumb;

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

Return DUMB;

}

Vector Result;

// ... Fill Result ...

Return Result;

}

*********************************************************** ****

WANG Tianxing School Note:

This example is not very persuasive. The scope of the three objects inside is not intersecting each other, so it is more likely to use RVO. This is the case: vector readfile ()

{

Vector Dumb;

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

Vector Result;

// ... Fill Result ...

RETURN ERROR? DUMB: RESULT;

}

*********************************************************** ****

Now there is a partial variable that needs to be mapped to the final result, they have several. Some is named (Dumb / Result), while others are unknown temporary objects. No need to say, facing such a situation, a large number of optimizers will surrender and obey the conservative and lack of efficiency.

Even if you want to write the "straight line" code that does not cause confusion of RVO, it will be disappointed because of the rules that you have to detect and apply RVO because you hear each compiler or compiler version. Some RVO applications are only a function that returns an unknown temporary object, which is the simplest RVO form. One of the most complex RVO applications is that the function return value is a naming result called naming return value optimization (NAMED RVO or NRVO).

Essentially, when writing procedures, you should count on the portable RVO, depending on your code's precision writing (in a very difficult "precise" sense), depending on the moon, depending on the moon, depending on your shoe size .

However, don't be busy, there are many cases where RVO can't avoid the copy of the temporary object. Compilers often not apply RVO, even if it thinks. Consider the call of readfile () after a little change:

Vector

VEC;

Vec = readfile ();

This change looks completely no malicious, but it has led to a huge difference. The copy constructor is no longer invoking the copy constructor (Assignment Operator), which makes a different unreasonable horse. Unless the compiler optimization skill is completely like using magic, you can now kiss, you can kiss if you have RVO: Vector :: Operator = (Const Vector &) expects a constant reference to a vector, so ReadFile will return a temporary Object, bind to a constant reference, copy to VEC, and then discarded. Unnecessary temporary objects come again!

In terms of coding, a long-term recommended technology is COW (on-demand copy, copy-on-write) [4], which is a trick based on reference count.

COW has several advantages, one of which is to detect and eliminate unnecessary replication. For example, when the function returns, the reference count of the returned object is 1. The reference count increases to 2 when copying. Finally, when destroying temporary objects, the reference count returns to 1, the reference pointing point is only the owner of the data. There is actually no copying movement.

Unfortunately, the reference count has a lot of defects in multi-threaded security, increasing their overhead and a large number of hidden traps [4]. COW is so clumsy, so although it has many advantages, the recent STL implementation is not std :: string uses reference count, although actually std :: string interface is designed to support reference count!

Several ways have been developed to implement "non-copy" objects, and Auto_Ptr is one of the most refined. Auto_ptr is easy to use correctly, but unfortunately, it is easy to use. The solution to the discussion herein extends the technique defined in Auto_PTR.

4 Mojo

Mojo (Joint Object Transfer, Move of Joint Objects) is a coding technology that is also a small frame that eliminates unnecessary temporary object replication. Mojo works by identifying temporary objects and legitimate "non-temporary" objects. 4.1 Transfer function parameters

Mojo triggered an interesting analysis, an investigation of function parameters transfer. General recommendations before Mojo are:

[Rule 1] If the function attempts to change the parameters (which is side effect), the parameters are passed as a pointer or reference to the very amount of object. E.g:

Void Transmogrify (Widget & Tochange);

Void Increment (int * ptobump);

[Rule 2] If the function does not modify its parameters and the parameters are the basic data type, the parameters are passed according to the value. E.g:

Double Cube (Double Value);

[Rule 3] Otherwise, the parameter is the user-defined type (or the type parameters of the template) and must be changed as a constant reference. E.g:

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

Template Vector :: Push_Back (const t &);

Article 3 Rules Attempt to avoid the copy of the unexpected large object. However, sometimes the third rule enforces unnecessary replication instead of blocking it. Consider the following CONNECT functions:

Void Canonicalize (String & URL);

Void Resolveredirections (String & URL);

Void Connect (Const String & URL)

{

String firmurl = URL;

Canonicalize (FinalURL);

Resolveredirections (FinalURL);

// ... use FinalURL ...

}

The Connect function gets a constant reference to the parameters and quickly creates a copy. Then further process the copy.

This function demonstrates the use of parameters that affect the efficiency. Connect's function declaration suggests: "I don't need a copy, a constant reference is enough", and the function body actually created a copy. So if you write this now:

String Makeurl ();

// ...

Connect (Makeurl ());

It is expected that MakeURL () will return a temporary object, he will be copied and destroyed, which is a fearless copy mode. For a compiler of optimized replication, it has to be very difficult, one is to access the definition of the Connect function (this is difficult for separating the compilation module), and its second is to parse the definition of the Connect function and further understand it. The third is to change the behavior of the Connect function to fuse the temporary object and FinalURL.

If the Connect function is now rewritten as follows:

Void Connect (String URL) / / Note Package

{

Canonicalize (URL);

Resolveredirections (URL);

// ... use URL ...

}

From the perspective of Connect's perspective, there is absolutely no difference: although the syntax interface is changed, the semantic interface is still the same. For compilers, changes in grammar have changed all things. Now the compiler has more room for concern the URL temporary object. For example, in the example mentioned above:

Connect (Makeurl ());

The compiler is not necessarily intelligent to fusion of the temporary object returned by MakeURL and the constant required by the Connect function. If you do it, it is really more difficult. In the end, the true result of Makeurl will be changed and used in the Connect function. The version using constant reference parameters will suffocate the compiler and prevent it from implementing any optimization, and use the version of the pass value parameter and the smooth cooperation of the compiler. This new version is unfavorable in that the CONNECT is now called more machine code. consider:

String SomeURL = ...

Connect (Someurl);

In this case, the first version is simple to deliver someURL reference [Translation note: from very placed to constant is a standard transformation]. The second version creates a SomeURL copy, calls Connect, then destroy the copy. With the increase in static quantity of CONNECT, the cost of the code size increases at the same time. On the other hand, for example, CONNECT (MakeurL ()) is introduced into a temporary object, and less code is generated in the second version. In most cases, size differences seem to cause problems [Demon] A problem, such as embedded application environment in some small memory applications.

So we gave a set of different recommendations:

[Rule 1] If the function is always created in the function, the value is passed. [Rule 2] If the function never copies the parameters, passed according to constant references. [Rule 3] If the function is sometimes replicating parameters, and the efficiency is careful, follow the MOJO protocol.

Now leaving only the development MOJO protocol, no matter what it is.

The main idea is to override the same functions (such as Connect), which is to distinguish between temporary and non-temporary values. The latter is also known as a left value, because historical reasons, the left value is named after the left side of the assignment operator.

Now you start to overrunate Connect, the first idea is to define Connect (const string &) to capture constant objects. However, this is wrong, because this statement "swallowed" all String objects, whether it is a left value (LVALUE) or temporary object [Translation note: Previous mention, very much implicit transformation into constants, this is a standard transformation action]. So the first good idea is not to declare the parameters that receive constant references, because it is like a black hole, swallow all objects.

The second attempt is to define Connect (String &) attempt to capture a very amount of left value. This is good, especially the constant value and the unknown temporary object cannot be accepted by this overloaded version, which is a good start. Now we only have a distinction between constant objects and very amount of temporary objects.

In order to achieve this, we have taken a technology to define two avive types [Translation] The original is type sugar, hey, if you want, you can call him type sugar if you like to eat sugar. ConstantString and TemporaryString, and define from the String object to these object transition operators:

Class string;

// Constant String's avatar type

Struct constantstring

{

Const string * obj_;

}

// Temporary String's avatar type

Struct TemporaryString: Public constantstring {};

Class String

{

PUBLIC:

// ... constructor, destructor, operator, etc. ...

Operator constantstring () constant

{

ConstantString Result;

Result.Obj_ = this;

Return Result;

}

Operator temporarystring () {

TemporaryString Result;

Result.Obj_ = this;

Return Result;

}

}

Now define the following three overload versions:

// Bind a very amount of temporary object

Void Connect (TemporaryString);

/ / Bind all constant objects (left value and temporary object)

Void Connect (constantstring);

// Bind a very amount of left value

Void Connect (String & Str)

{

// Call another overload version

Connect (constantString (str));

}

Constant String objects are absorbed by Connect (constantString). There is no other binding to work, and the other two are only called the String object.

Temporary objects cannot call Connect (String &). However, they can call Connect (TemporaryString) or Connect (ConstantString), which must be selected without ambiguity. The reason is because TempoRingString is derived from ConstantString, a trick you should pay attention to.

Consider constantString and TempoRingString are independent types. Then, when a temporary object is required, the compiler will treat Operator TemporarY () / Y (Temporary) or Operator constant () const / y.

Why is it equally? Because you choose a member function, it is very much to the constant transformation is "free friction".

Thus, there is a need to tell the compiler More choices of the first instead of the second. That is to inherit the role here. Now the compiler says: "Okay, I guess I have to pass ConstantString or TempoRingString ..., but wait, derived TemporaryString is a better match!"

The rule here is to select a function from the overload candidate, the matching derivative class is considered better than the base class.

[Translation]

I am a modification of the above code, derive string from std :: string, and in this basis, according to Mojo, the result is true that the author pointed out in the GCC3.2 compiler. This overloaded resolution rule rarely mentioned in C books, Wang Tianxing found this rule from the vast standard text of smoke:

13.3.3.2 Ranking Implicit Conversion Sequences [over.rank]

4 [...]

- IF Class B Is Derived Directly or Indirectly

From Class A and Class C Is Derived Directly

Or Indirectly from B,

[...]

- Binding of An Expression of Type C to A

Object of Type B Is Better Than Binding

An Expression of Type C to a Object

Of Object A,

The terms in these standards are the elements of the implicit transformation level, which is roughly meaning that if c inherits B, and B inherits A, then type C expression binds to B object ratio The object to a is better, which is the standard basis for the technology described above. In addition, similar references and pointers are also applicable to this rule, these provisions are omitted.

The last interesting figure is that inherits don't need to be public. Acquisition rules and overload rules are not conflict.

Let's take a look at how Connect works:

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

// Call Connect (String &)

Connect (S1);

// Call Operator TempoRingString ()

/ / Next call Connect (TemporaryString)

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

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

// Call Operator ConstantString () Const

/ / Next call Connect (constantstring)

Connect (S4);

As you can see, we have achieved the main goals of the expectations: a difference between the temporary object and all other objects. This is the principle of Mojo.

There are still some uncommon problems, most of us have to solve one by one.

The first is to reduce the code repeat: Connect (String &) and Connect (constantString) basically do things. The above code called the second overload function by the first overload function to solve this problem.

Let us face the second question, write two small classes for each type of Mojo, it is not very attractive, so let us start making some more general things easier to use. We defined a Mojo namespace and put them in two generic Constant and Temporary classes:

Namespace Mojo

{

Template

Class Constant

{

Const T * DATA_;

PUBLIC:

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

{

}

Const T & Get () Const

{

Return * Data_;

}

}

Template

Class Temporary: Private Constant

{

PUBLIC:

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

{

}

T & Get () const

{

Return const_cast (constant :: get ());

}

}

}

Let's define a base class mojo :: enabled, which includes two operators:

Template struct enabled // In Mojo namespace

{

Operator Temporary ()

{

Return Temporary (static_cast (* this));

}

Operator constant () const

{

Return Constant (static_cast (* this));

}

protected:

enabled () {} // can only be derived

~ enabled () {} // can only be derived

}

With this "Scaffold", you can imagine the task of "Mojo", you can think of it easier:

Class String: Public Mojo :: Enabled

{

// ... Constructor, destructor, operator, etc. ...

PUBLIC:

String (Mojo :: Temporary TMP)

{

String & rhs = tmp.get (); // ... Execute rhs to * this destructor copy ...

}

}

This is the MOJO protocol that passes the function parameters.

Usually, everything is working well, you got a good design. Yes, those accidents are controlled in a small range, which makes them more valuable.

Designing with Mojo We can easily detect if a class supports Mojo. Simply write:

Namespace Mojo

{

Template

Struct Traits

{

ENUM

{

Enabled = LOKI :: SupersubclassStrict , T> :: Value

}

}

}

LOKI provides a mechanism that detects a type from another class. [5]

You can now find an arbitrary type X is designed according to Mojo protocol, as long as you can be determined by MOJO :: Traits :: enabled. This test mechanism is very important to generic programming, so soon we will see its role.

4.2 Function Return Value Optimization

Now we can pass the parameters correctly, let us see how to extend the MOJO to the function return value optimization. The purpose of this time is the improvement of portability, that is, 100% elimination is not required to rely on a specific return value optimization (RVO) implementation.

Let's take a look at the usual suggestions. For good intentions, some author also recommend the rules of the return value [7]:

[Rule 4] When the function returns the value of the user-defined object, a constant value is returned. E.g:

Const String Operator (Const String & lh, Const String & RHS);

The subtext of the rule 4 is that the user-defined operator is more close to the built-in operator to prohibit the function of the error, it seems to be if IF (S1 S2 == S3), the pen misunderstood IF (S1 S2 = S3). If Operator returns a constant value, this particular bug will be detected during the compilation [Demon note: Returns the value of the built-in data type is always constant, and the user-defined type requires explicit use constant qualifiers. Pointed out]. However, other author [6] is recommended not to return a constant value.

Calmly, any return value is short, it is a short-lived ghost that is just created. So why do you have to force the operator to get a constant value? From this point of view, the temporary object of constant looks like it is self-contradictory, which is both unchanged and temporary. From the perspective of practice, constant objects are forced to copy.

Now suppose we agree, if efficiency is important, it is best to avoid return values ​​to constants, then how do we make the compiler confident that the function's results are transferred to the destination, not replicating him?

When a type T is replicated, the copy constructor is called. Follow the settings below, we just provide such a copy constructor to achieve this goal.

Class String: Public Mojo :: Enabled

{

// ...

PUBLIC:

String (string &);

String (mojo :: temporary );

String (Mojo :: constant );

}

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

Because the copy constructor is not exactly the same, especially for a type X, the following code will not work with the code that requires X (Const X &), the following code will not work: void functionTakingTakingX (const X " ;

FunctionTakingTakingX (x ()); // Error! Can't find X (Const X &)

[Translation]

WANG Tianxing In GCC3.2, BCC5.5.1, ICL7.0 environment, the test results show that there will be no errors, and then check the standard, and find that Andrei is correct. If you must say something wrong, he did not point out this. It is implemented in implementation.

8.5.3 REFERENCES

5 [...]

- IF The Initial Expression IS An Rvalue, with T2 A Class Type,

And "CV1 T1" Is Reference-Compatible with "CV2 T2," THE REFERENCE

Is Bound in One of the Following Ways (The Choice Is Implementation

Defined):

- The reference is bound to the object represented by the RValue

(See 3.10) OR to a sub-object within there.

- a Temporary of Type "CV1 T2" [SiC] is created, and A

Constructor is Called to Copy The Entire Rvalue Object Into Thae

Temporary. The reference is bound to the temporary or to a

Sub-Object within the temporary.93)

The Constructor That Would Be Used to Make The Copy Shall B

Callable WHETER OR NOT The Copy Is Actually Done.

93) Clearly, if The Reference Initialization Being Processed Is One

For The First Argument of a Copy Constructor Call, An Implementation

Must Eventually Choose The First Alternative (Binding With)

TO Avoid Infinite Recursion.

I quote this standard text, interested readers can study their meaning.

This is seriously limited to X, so we are forced to implement String (const string &) constructor. Now if you allow me to quote this article, I have said before: "So the first good idea is not to declare a function to accept constant references, because it swallowed all the objects like a black hole."

Fish and bear's pauses are not part, isn't it?

It is clear that the copy constructor requires special processing. The idea here is to create a new type of FnResult, which is a "Mover" "for the String object. Below is the steps that need to be executed:

The function of the value of the type T is now returned to FnResult . In order to make this change to the caller, the FnResult must be implicitly transformed into T. Then establish a transfer semant to FnResult: Whenever a FnResult object is copied, the T is transferred. The constant and temporary similar to operators, providing a transition operator for FnResult in the Mojo :: Enabled class. A MOJO class (String in the foreiot) defines a constructor string (Mojo :: FnResult ) completes the transfer. The definition of this FnResult looks like:

Namespace Mojo

{

Template

Class FnResult: Public T

{

PUBLIC:

FnResult (Const FnResult & RHS)

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

{

}

Explicit FnResult (T & RHS): T (Temporary (RHS))

{

}

}

}

Because FnResult comes from T, the first step is worth noting that fnResult transformation is T, and then the second worth paying attention is to copy the FnResult object, implied with its T SubObject forced transformation into Temporary .

As mentioned earlier, we add a transformation that allows you to return to a fnResult, and the last version looks like this:

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 () {} // intended to be deived from

~ enabled () {} // intended to be deived from

}

Finally, String definition:

Class String: Public Mojo :: Enabled

{

// ...

PUBLIC:

// Copy RHS

String (const string & rhs);

//Move tmp.get () INTO * THIS

String (Mojo :: Temporary TMP);

//Move res inco * this

String (Mojo :: FnResult Res);

}

Now consider the following functions:

Mojo :: fnResult MakeString ()

{

String result;

//? ..

Return Result;

}

// ...

String dest (makeString ());

The path between MakeString Return statements and DEST definitions is:

Result -> String :: Operator FnResult () -> FnResult &) -> String :: String (FnResult )

The compiler using the RVO can eliminate the call of FnResult &) in the call chain. However, more importantly, there is no function to perform real replication, which are defined as the result of the actual content smooth transfer to DEST. That is, there is no memory allocation and replication.

Now, as seen, there are two, up to three transfer operations. Of course, at a certain condition and a certain type of case, a copy may be better than three transfers. There is also an important difference, replication may fail (throw an exception), and the transfer will never fail.

5 expansion

Ok, we have worked Mojo and is quite good for individual classes. Now how to extend MOJO to a combined object, which may contain a lot of other objects, and some of them are Mojo.

This task is to pass the transfer constructor from the class to a member. Consider the following example, the inline string is in the class widget:

Class Widget: PUBLIC MOJO :: Enabled

{

String name_;

PUBLIC:

Widget (Mojo :: Temporary SRC) // Source Is A Temporary

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

{

Widget & rhs = src.get ();

// ... Use rhs to perform a destructive copy ...

}

Widget (Mojo :: Constant src) // Source Is A Const

: name_ (src.get (). name_) // Translation: Here is the original Name_ (src.name_) obviously incorrect

{

Widget & rhs = SRC;

// ... Use rhs to perform a destructive copy ...

}

}

Initialization of Name_ in the transfer constructor uses an important MOJO auxiliary function:

Namespace Mojo

{

Template

Struct Traits

{

ENUM {enabled =

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

Typedef Typename

Loki :: SELECT , T &> :: Result Temporary;

}

Template

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

{

Typedef Typename Traits :: Temporary Temp;

Return Temp (SRC);

}

All things made by As_TemPorary are based on a left value to create a temporary object. Using this method, the transfer constructor of the class member is called by the target object.

If string is Mojo, Widget gets his advantage; if not, a direct copy is executed. In other words, if String is a derived class of Mojo :: Enabled , as_temporary returns a mojo :: temporary . Otherwise, as_temproary (string & src) is a simple function with a string & parameter and returns the same String &.

6 Applications: relatives and Moj libly containers of auto_ptr

Consider a Mojo_PTR class, which prohibits them by making the copy constructor privately:

Class Mojo_ptr: Public Mojo :: Enable

{

Mojo_ptr (const mojo_ptr &); // const Sources Are Not Accepted

PUBLIC:

// Source Is A Temporary

Mojo_ptr (Mojo :: Temporary SRC)

{

MOJO_PTR & RHS = src.get ();

// ... Use rhs to perform a destructive copy ...

}

// Source is a function's result

Mojo_ptr (Mojo :: FnResult SRC)

{

MOJO_PTR & RHS = src.get ();

// ... Use rhs to perform a destructive copy ...

}

// ..

}

This class has an interesting behavior. You can't copy the constant objects of this class. You can't copy the left value of this class. But you can copy this class's temporary object (using transfer semantics), and you can explicitly move an object to another object:

MOJO_PTR PTR1;

Mojo_ptr ptr2 = mojo :: as_temporary (PTR1);

This is not very dense, if Auto_Ptr (Auto_Ptr &) is private, you can do this if auto_ptr is private. Interesting place is not Mojo_Ptr itself, but how to use as_temporary. You can build an efficient container, store the type of "classic", the typical MOJO type and similar types similar to Mojo_Ptr. All such a container must use as_temporary when he needs to transfer an element. For the "classic" type, as_temporary is an equivalent function that does not do, for Mojo_Ptr, as_temporary is a function book that provides smooth transfer mechanism. Move () and uninitialized_move () Function template (see the attached code, the translation: code, please go to the original link) also can be available.

Using standard terms, MOJO_PTR is neither copy, nor can it be assigned. However, MOJO_PTR can be seen as a new type called "transferable". This is an important new classification that may be available for lock (LOCK), file (file), and other unabled handles (HANDLE).

If you have hoped a container with an element similar to Vector >, there is safe, clear semantic, now you get, and there are other features. In addition, when including a copy of an expensive type, such as Vector >, Mojo's VECTOR "can adapt to the need for elevale reduction or decrease." 7 conclusions

Mojo is a technology and a compact small frame for eliminating the copy of unnecessary temporary objects. Mojo's working mode is to detect temporary objects and overload them through a function, but not simply as a left value. The result of this is that the function of obtaining the temporary object performs a destructive copy, as long as it is confident that other code is no longer used by this temporary object.

If the customer code passes the function parameter and the return value according to a simple rule, you can apply MOJO.

Mojo defines a separate mechanism to eliminate replication when the function returns.

Additional mechanisms and type conversions make MOJOs are not 100% transparency to customer code, however the integration is quite good for library-based solutions. Something, Mojo will be a robust alternative until a more robust, based on language characteristics, is standardized and implemented.

8 thank you

The original consistency of the original text, the translation got the enthusiasm of Wang Tianxing, in addition to helping me review a number of technical details, I also pointed out a lot of typing errors, and proverbs in several English.

9 reference

[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).

About the Author

Andrei Alexandrescu is a book by a Ph.D. student, a widely acclaimed "Modern C Design" (Chinese translation modern C Design). You can contact your email andrei@metalanguage.com. Andrei is also a tutor of a C course.

Translator's words

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

New Post(0)