Wild: Enforcements

xiaoxiao2021-03-06  125

Wide : Implementation (Enforcements)

Andrei alexandrescu and petru marginean

This article code download

You know, when a concept becomes a normal noun from a proprietary noun, it means that it really goes deep into the heart. For example, Klenex (noodle paper, also facial towel), Xerox (Xerox, copier brand, also referred to copy machine) Q-TIPS (cosmetics brand, also a cosmetic bag), right? So, when I heard that you can use "Modern C Design" in Visual C . Net is very happy. Here, it is - at least I think - "MODERN C Design" [1] is a template-based technology.

There is a successful element in all generic series of articles, is to conduct a universal "text letter sublimation.", In ScopeGuard [2], add "undo" action to normal execution path After the complex operation is successful, the ScopeGuard has become "Scope Guard" when complex operation is successfully "revoked". Most popular articles are not completely completed by me, but the results of close cooperation with Petru Marginean. And let me be more happy to work with him again in this article.

Last, we discussed assertions, a powerful design verification and debug mechanism. Today, we will discuss the mechanisms that assert that correspond to the publication state: Enforcement, which is convenient and practical. Very similar to ScopeGuard, Enforce macro decently reduces the code you need to use on error handling. ScopeGuard and Enforce work independently, but it is best to use it together. Enforce is an unexpected source, and ScopeGuard is an unexpected communication.

Enforcements

Imagine a lot of code you look at. You know that you have to kill a blood road in these code, you also know that you must write more new code.

A good habit is to browse the code first, try to find some general mode. You have to understand the concept behind the pattern. It is very likely that all these are not accidentally happening, but the same basic concepts of the same basic concept, then you can organize these extracted patterns so you can briefly express all models of implementation as a concept.

Pause now, look at the size of different modes you want to analyze. If this mode is large, for example, the entire program is involved as the foundation part, then you are dealing with architecture mode (Architectural Patterns).

If the mode is medium size, across several objects and / or functions, then you are in design mode.

If the mode is very small, only 3-10 line code, then the commonality (IDiom) in front of you.

Finally, if the mode is only 1-2 line code, what you get is called the code style or format.

These four ranges covered according to the type of scale are similar to architecture. In architecture, the basic structure of the basic structure is not tight. In the software architecture, any flaws may destroy the entire "building". Instead, you need to use the right technique in any range to ensure success. If you have a big blueprint for small details, you will waste the complex Arabic buildings that are unable to build. If you only pay great attention to the details, you will get rough phenomenon.

This is the reason for writing software unusual difficulties. This difficulty is in other fields that cannot fully understand.

Bring this.

Implementation belongs to the common method. In detail, the implementation greatly simplifies the erroneous detection code without affecting the smoothness of readability and normal workflow.

The idea comes from the following facts. You throw an accident and is very likely that as a result of a Boolean value, as follows:

IF (Some test)

Throw someexception (arguments);

If the accident appears in multiple, why not put it in a small function:

Template

Inline void Enforce (Bool Condition A arg)

{

IF (! condition) THROW E (Arg);

}

Can use it like this:

Widget * p = makewidget ();

Enforce (p! = 0, "null pointer");

Enforce (cout! = 0, "cout is in error");

Cout << p-> toString ();

Everything is going on so far. Now let's analyze a few important aspects.

First, the conditioned conditions are not always Boolean, or it may be a pointer or integer. Second, it is very likely that you want to use the measured value immediately after testing. For example, you may want to make sure that a pointer is not empty before use, or you might want to use it immediately after creating a file handle. So we modify Enforce so that it has a filtering mechanism to pass the received value back:

Template

Inline T & Enforce (T & Obj, A Arg)

{

IF (! obj) throw e (arg);

Return Obj;

}

Template

Inline Const T & Enforce (Const T & Obj, A arg)

{

IF (! obj) throw e (arg);

Return Obj;

}

(Two versions are required, corresponding to const and non-const objects, respectively.) You can add two overload versions to indicate the unexpected appearance you usually and the parameters it belongs.

Template

Inline T & Enforce (T & Obj, Const Char * Arg)

{

Return this-> Enforce (OBJ,

arg);

}

Template

Inline Const T & Enforce (Const T & Obj, Const Char * Arg)

{

Return this-> Enforce (OBJ,

arg);

}

If you think it should be passed into a general parameter (information) to std :: runtime_error, the call can be further simplified. Some of what you need is just adding a few overload functions:

Template

Inline T & Enforce (T & Obj)

{

Return this-> Enforcemstd :: runtime_error, const char *, t> (Obj,

"Enforcement Error");

}

Template

Inline const t & enforce (const t & obj) {

Return this-> Enforce (OBJ,

"Enforcement Error");

}

Now, as these simple extensions, the code becomes quite expressive:

Enforce (cout) << Enforce (Makewidget ()) -> TOSTRING ();

In a row, you don't just create a Widget object and print it to the console, and you also indicate any errors that may happen during this process! You may have to automatically release the created Widget object, just add auto_ptr inside:

Enforce (COUT) <<

Enforce (Auto_Ptr (Makewidget ())) -> TOSTRING ();

Wow! Very good -, especially when comparing it and other solutions.

In the case where the normal execution process is not interrupted, Enforce is beautifully filtered out of the error. In this way, EnForce provides a convenient means to check and clear the error.

It is very important to allow programmers to easily handle errors. This is because the error handling is often, unfortunate, is considered a white feet. Managers do not handle errors as an assessment standard. As a result, in a hurry, excessive, preparation [3], the programmer is in a good state, Makeshape will never return empty pointer. But the bite finger is not a truly good programming method.

Modification enforce

The "Enforcement Failed" information displayed in the above code is not very useful, so we need to modify it. Fortunately, Petru's inspiration is unpacking. "The head never stops working!"

First, the good information contained in the error notification should have __file__ and __line__, __line__. At the same time, it will be helpful in seeing failed expressions. Just like we do in Asserter [4], we build a small class to save us:

Template

Class Enforcer

{

REF OBJ_;

Const char * const Locus_;

PUBLIC:

Enforcer (Ref obj, const char * locus): OBJ_ (OBJ), LOCUS_ (LOCUS) {}

Ref enforce ()

{

IF (! obj_) throw std :: runtime_ERROR (LOCUS_);

Return Obj_;

}

}

OBJ_ member saves the detected object. Locus_ member is the above information about documents, lines, and expressions.

Why do we call Enforcer's template parameters Ref instead of traditional T? The reason is that we must always instantiate Enforce with a reference type (not a value type), which will reduce our repetition labor. (If you have written similar functions to const and non-const referenced, you will know what I mean)

Ok, now create an Enforcer object, we use a small function so that we can easily, let it make type inference:

Template

Inline Enforcer

Makeenforcer (Const T & Obj, Const Char * Locus)

{

Return Enforcer (OBJ, LOCUS);

}

Template Inline Enforcer

Makeenforcer (Const T & Obj, Const Char * Locus)

{

Return Enforcer (OBJ, LOCUS);

}

We only need to give the cake on the cream - the macro in the expectation.

We know that you hate the macro, and hate the macro is not in a small number, but we hate repeat __file__ and __line__:

#define stringize (Something) stringize_helper (Something)

#define stringize_helper (Something) #Something

#define enforce (EXP) /

Makenforcer ((Exp), "Expression '" #exp "' Failed in '" /

__File__ ", line:" Stringize (__ line __)). Enforce ()

Stringize and stringize_helper macros are the complex procedures necessary for the precorator to convert __line__ to numbers. (No, # __ line__ is not used.) I have never known that these macros have a role (this is related to the pre-compiler) ... Ah, my mind began to emerge in the tragic memories! Stop, doctors!) - Moreover, confession, I am willing to understand how the New York City's sewer system does not want to know the details here. As long as Stringize (__ line__) produces a string containing the current number of rows. Those expert [6] provide a complete explanation.

The consistent tradition of this column does not involve the compiler characteristics, so we only make the Stringize tip will produce a mystery string similar to (__line__var 7) on the MSVC's pre-compiler.

The happy side is that the initialization of Enforcer has only two pointers to assign the value, but it saves very useful information. You can easily increase information about file dates and compile time, as well as non-standard information, such as __function__.

Support multiple parameters and custom judgment conditions for Enforce

Enforce is a good idea, but if you use something, it is found that it is not as useful in practical applications, is you not being deceived?

We will, and we have found two important defects in Enforce.

First, you usually need to increase the function of the default file name, row number, and expression information, or from the function of a custom string.

Second, Enforce is only used! Operators detect non-zero conditions. However, in real applications, the "error" value that needs to be checked is not zero. Many APIs using integer return values, including standard C file functions in , returns -1 to identify an error. Other APIs use a symbol constant. And COM uses more complex case: If the return value is zero (already S_OK), it means normal, if the return value is less than zero, the description has an error, and the actual value returned gives an error message. If the return value is greater than zero, the status is "successful with information", that is, there are some useful information in the return value [5].

Obviously we need a more flexible testing and reporting framework. We need to be able to configure implementation on both levels (judgment conditions and parameters), preferably in compile time configuration, such implementing mechanisms that do not have more overhead than the equivalent handwritten code. (There is a wise check that always needs to be done: When some abstract applied to the specific situation, is it possible to get a non-abstract solution?) Policy-based design is suitable for resolving this issue. Therefore, Enforce needs to improve the template class for a dual-policy parameter from a simple class. The first policy is to determine the condition policy (processing detection), the second policy is to throw the policy (processing build and throw an accident).

Template

Class Enforcer

{

... Use two strategies (see part) ...

}

Both strategies have a very simple interface. The following is the default policy:

Struct defaultpredicate

{

Template

Static Bool WRONG (Const T & Obj)

{

Return! OBJ;

}

}

Struct Defaultraiser

{

Template

Static Void Throw (Const T &, Const Std :: string & message, const char * locus)

{

Throw std :: runtime_ERROR (Message '/ N' Locus);

}

}

Implement detail (and beautiful skills)

Ok, now let Enforcer use its two strategies to detect values ​​and throw accidents. It should be simple.

If an error occurs, it will be useful if the user can specify any information format; further unless an unexpected is really thrown, this information (possibly affecting the speed) should be avoided. Some inspiration add 99% of sweat, we designed a mechanism that satisfies these requirements.

Let's present the code and do it. The final Enforcer class is as follows:

Template

Class Enforcer

{

PUBLIC:

Enforcer (Ref T, Const Char * Locus): T_ (t), LOCUS_ (P :: Wrong (T)? Locus: 0)

{

}

Ref operator * () const

{

IF (Locus_) R :: Throw (T_, MSG, LOCUS_);

Return T_;

}

Template

Enforcer & Operator () (Const MsgType & MSG)

{

IF (Locus_)

{

// Do this is time, there is time, don't have too high efficiency

Std :: ostringstream ss;

SS << msg;

MSG_ = ss.str ();

}

RETURN * THIS;

}

Private:

Ref t_;

Std :: string msg_;

Const char * const Locus_;

}

Template

Inline Enforcer

Makenforcer (Const T & T, Const Char * LOCUS)

{

Return Enforcer (t, locus);

Template

Inline Enforcer

Makenforcer (T & T, Const Char * LOCUS)

{

Return Enforcer (T, LOCUS);

}

#define Enforcer (EXP) /

* Makenforcer (/

(EXP), "Expression '" #exp "' failed in '" /

__File__ ', line: "Stringize (__ line__))

Very good, so Enforce defines two operator functions: Operator * and template Operator (). And pay attention to the Enforce macro "*" before the Makenforcer call. What is the working principle of all these? Why do you want these auxiliary code?

Suppose you write down the following code:

Widget * pWidget = makewidget ();

Enforce (PWIDGET);

Enforce macro extension is:

* Makenforcer (fpwidget),

"Expression 'PWidget' FAILED IN 'Blah.cpp', Line: 7")

MakeenForcer is called after creating an object of the following type:

Enforcer

This object creates the constructor of two parameters. Please note that Locus_ only is initialized to a non-null pointer when p :: wrong (t) is true. In other words, LOCUS_ only points to useful information when it should be thrown at the accident, otherwise it is empty.

Call the Operator * function for the created object. Not yet, if locus_ is not empty, R :: throw is called, otherwise the object being detected is only back.

Continue to see a more interesting example, the code is as follows:

Widget * pWidget = makewidget ();

Enforce ("This Widget is Null and It Shouldn't!");

Here, when the Enforcer object is created, Operator () is called. This operation either adds the incoming information to the MSG_ member variable, or if the PWIDGET is non-empty, it is ignored all. In other words, the normal execution path is as fast as the execution path with a detection. This is a beautiful place - the real job is only doing the wrong situation.

Because Operator () is templated and uses a std :: ostringstream, it supports everything you can pass to Cout. Moreover, Operator () returns * this so you can connect a continuous call to it. The following example shows this:

INT n = ...;

Widget * pWidget = makewidget (n);

Enforce ("Widget Number") ("is Null and It Shouldn't!");

We don't know how you feel, anyway, we are very satisfied with this design. Conversely, who does not like a concise, expressive, and is an efficient solution?

Custom judgment and throwing strategy

Policy-based Enforcer provides important hooks to allow unlimited changes. For example, the detection handle value is not -1 (instead of detecting is not zero) only six lines of code:

Struct Handlepredicate

{

Static Bool Wrong (Long Handle)

{

Return Handle == -1;

}

}

#define handle_enforce (eXP) /

* Makenforcer ((exp), "expression '" #exp "' failed in '" /

__File__ ", line:" Stringize (__ line__)))

Don't forget that Enforce returns the incoming value, which gives the customer code to bring great expression:

Const int available = handle_enforce (_READ (File, Buffer, Buffle));

The above is read from a file, record the number of bytes read, and the error may occur. Well, cool!

Similarly, you can define your application for your application to define your new policies and XYZ_ENFORCE macros. There is a XYZ_ENFORCE macro for each error coding specification. Most applications use 1 to 4 different specifications. In practice, we have encountered the following common specification:

l The basic enforce we discussed above. Use Operator! Check and throw std :: runtime_exception.

l Handle_enforce. The detection is not -1 and throws an accident.

l COM_ENFORCE. When the result is negatively handled as an error. Throwing Policy extracts an error message from the COM return code and puts in an unexpected object to be thrown. We believe this tool is a true invaluable treasure when writing important COM applications.

l Clib_enforce. Many returns zero when the function in the C standard library is wrong and wants you to check Errno to know the error details. If there is a beautiful RAISER policy that can convert Errno to a text and put the text into an accident to thrown, Clib_enforce will be perfect.

l WAPI_ENFORCE. The negative error code is returned when the Windows API function returns to zero failure.

Adapting to the new error coding specification is a very simple thing.

in conclusion

We found that the value of the value is extremely useful, and even it is equivalent to having fun, losing it inch. Implementation is not to concentrate a few lines of code as a line of code. Implementation allows you to concentrate on the normal implementation stream of your application, naturally and flexible filtering out of the unwanted values. This filter is achieved by returning the filter function of its incoming parameter.

With a few macro skills, it has paid very low runtime overhead, adding useful information

The policy-based design is implemented through the parameters of the template parameters, which is not only strong in theory, but also the same in practice. Strategy-based methods generated by low runtime overhead (and handwritten IF) and adapt to high configurable frames that can be adapted to most special error coding specifications.

We have tested additional code with Microsoft Visual C . Net Everett Beta and GCC 3.2. Use it with confidence!

Reference and comments

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

New Post(0)