Traits Technology: Type of IF-else-then (one of STL core technology)

zhaozj2021-02-08  406

Traits: type ELSE-IF-THEN mechanism

Andrei alexandrescu

Andrei Alexandrescu is developing manager in RealNetworks in Seattle, Washington.

What is Traits, why people think it is important for C generic programming?

Briefly, Traits is so important because this technology allows the system to make some decisions based on the type of time, as if the runtime is determined according to the value according to the value. Further, this technique follows the proverb of "additional indirect layers", solves many software engineering issues, and Traits allows you to make a choice based on the context produced. Thus the final code becomes clear and easy to read, easy to maintain. If you use Traits technology correctly, you can get these benefits while doing any performance and security, or can fit the needs of other solutions.

Example: Traits are not only the core tools for generic programming, but also the following examples can make you believe that in a very specific issue, it is also useful.

Suppose you are writing a relational database application now. Maybe you start using the API library provided by the database vendor to perform the operation of the database. But after a matter of course, you will have to write some packaging functions to organize those original APIs, on the one hand, for the sake of brevity, on the other hand, can better adapt to the task in your hand. This is the fun of life, isn't it?

A typical API is this: providing a basic method to transfer the original data at the cursor (Cursor, a rowset, or query results) to memory. Now let's write a senior function to take the value of a column and avoid the detail of the underlying. This function may be this: (Imaginary DB API starts with DB or DB)

// EXAMPLE 1: Wraping a raw cursoor int fetch // Operation.// fetch an integer from the // curusor "cr" // at column "col" // in the value "Val" Void Fetchintfield (DB_CURSOR & CR, Unsigned int col, int & val) {// Verify type match if (cr.column_type [col] = dB_INTEGER) throw std :: runtime_error ( "Column type mismatch");! // Do the fetch db_integer temp;! if (db_access_column ( & cr, col)) Throw std :: runtime_ERROR ("Cannot Transfer Data"); Memcpy (& Temp, Cr.Column_Data [col], sizeof (temp)); // Required by the db api for cleanup db_release_column (& Cr, Col) ; // Convert from the database native type to int val = static_cast (temp);

This interface function may have to write over again in some time, it is not good to deal with but very important, handling a lot of details, and this is just a simple example. Fetchintfield abstraction, provides a high level of functionality, it can get an integer from the cursor, don't worry about it. Since this function is so useful, we can of course hope to use it as much as possible. But what do you do? A very important generalization step is to let this function can handle the type other than INT. In order to do this, we have to carefully consider the part of the code in the INT type. But what is the meaning of DB_INTEGER and DB_INTEGER, what are they playing? This, the relational database vendor usually provides some type-mapping helpers with the API, defines a symbol constant or TypeDef for each type and a simple structure supported, and corresponding to the C / C type.

Below is a false database API header file:

#define DB_INTEGER 1 # define DB_STRING 2 # define DB_CURRENCY 3 ... typedef long int db_integer; typedef char db_string [255]; typedef struct {int integral_part; unsigned char fractionary_part;} db_currency; ...

We tried to write a fetchdoublefield function as the first step toward genericization. This function gets a Double value from the cursor. The type image provided by the database itself is db_currency, but we hope to operate in the form of Double. Fetchdoublefield looks very similar to Fetchintfield, which is simply twin brothers. Example 2:

// Example 2: Wrapping a raw cursor double fetch operation.//void FetchDoubleField (db_cursor & cr, unsigned int col, double & val) {if (! Cr.column_type [col] = DB_CURRENCY) throw std :: runtime_error ( "Column type mismatch "); if (db_access_column (& cr, col!)) throw std :: runtime_error (" Can not transfer data "); db_currency temp; memcpy (& temp, cr.column_data [col], sizeof (temp)); db_release_column (& cr , col); val = Temp.integral_Part Temp.fractionary_part / 100 .;}

It looks like Fetchintfield! We don't want to write a separate function for each type, so if you can bring FetchintField, FetchDoublefield, and other Fetch functions in one place.

We will include the differences between these two codes as follows:

· Input Type: Double / INT · Internal Type: DB_Currency / DB_INTEGER · Constant Value Type: DB_CURRENCY / DB_INTEGER · Algorithm: An Expression / Static_cast

The correspondence between the input type (int / double) and other points seems to have no rules, but very casual, close to the type of relationship provided with the database vendor (just). The Template mechanism itself does not work, it does not provide such an advanced type of reasoning mechanism. It is also possible to organize different types with inheritance relationship because we deal with the original type. Affected by the API and the underlying characteristics of the problem itself, when we seem to have nothing. However, we have a living road. Entering Traits Door: Traits Technology is used to solve the above problems: the ability to discharging the same type, and has the ability to have similar AND / OR structures, which can produce different variants according to different types. Traits rely on Explicit Template Specialization mechanisms to get this result. This feature allows you to provide a separate implementation of template classes for every specific type, see Example 3:

// EXAMPLE 3: A Traits Example // Template Class SometerMplate {// generic ustementation (1) ...};

// Note the specific syntax of the following Template <> Class SyleETemplate {// Implementation Tuned for char (2) ...}; ... syleemplate a; // Will Use (1) syleemplate b; // Will Use (1) sometemplate c; // Will Use (2)

If you use a char type to instantiate the SyleETemplate class template, the compiler will be specialized with the explicit template declaration. As for other types, of course, use that universal template to instantiate. This is like a type driven IF-Statement. Usually the most common template (equivalent to the else section) is first defined, if the if-statement is later. You can even decide not to provide universal templates at all, so that only specific instantiation is allowed, others can cause compilation errors.

Now let's link this language characteristic with your hand. We want to implement a template function fetchfield, instantiate as a parameter using the type you need to read. In this function, I will represent the symbol constant with a thing called TypeID. When I want to get the INT value, it is db_integer, when I want to get the Double value, it is db_currency. Otherwise, you must report the error in compile time. Similarly, according to the type of type to be acquired, we also need to operate different API types (DB_INTEGER / DB_CURRENCY) and different conversion algorithms (express / static_cast).

Let us use the explicit template specialization mechanism to solve this problem. We have to have a fetchfield that can produce different variants for a template class, and that template class can explicitly specialize in INT and DOUBLE. Each specialization must provide a unified name for these variants.

// Example 4: The most common case Defining DbTraits //// Most general case not implemented not implemented struct DbTraits template ; // Specialization for inttemplate <> struct DbTraits {enum {TypeId = DB_INTEGER}; typedef db_integer DbNativeType; // Convert Note that the following is static member function - Translator static void Convert (DbNativeType from, int & to) {to = static_cast (from);}}; // Specialization for doubletemplate <> struct DbTraits {enum {TypeId = dB_CURRENCY}; typedef db_currency DbNativeType; // Convert Note that the following is static member function - Translator static void Convert (const DbNativeType & from, double & to) {to = from.integral_part from. Fractionary_part / 100 .;}}; now, if you write dBTraits :: typeid, you get DB_INTEGER, and for DBTraits :: typeid, get db_currency, for dbtraits :: typeid What is it? Compile-Time Error! Because the template class itself is only declared, it is not defined.

Is it always for all? See how we use DBTraits to achieve fetchfield. We put all the changes - enumeration types, API types, and conversion algorithms - all in dbtraits, which only contains the same part of FetchintField and FetchDoublefield in our function:

// Example 5: A generic, extensible FetchField using DbTraits // template void FetchField (db_cursor & cr, unsigned int col, T & val) {// Define the traits type typedef DbTraits Traits; if (cr. ! column_type [col] = Traits :: TypeId) throw std :: runtime_error (! "Column type mismatch"); if (db_access_column (& cr, col)) throw std :: runtime_error ( "Can not transfer data"); typename Traits: : DBNATIVETYPE TEMP; Memcpy (& Temp, cr.column_data [col], sizeof (temp)); Traits :: Convert (Temp, Val); DB_RELEASE_COLUMN (& Cr, COL);} Fast! We only achieve and use a traits template class!

Traits rely on explicit template specialization to drag out the code in the code, and package it with a unified interface. This interface can contain anything that can be included in a C class: embedded type, member function, member variable, as a customer's template code can be indirectly accessed through the interface disclosed by the Traits Template class.

Such Traits interfaces are usually implicit, implicit interface is not as strict, for example, although dbtraits :: Convert and dbtraits :: convert have very different signatures, but they all Can work normally.

The Traits Template class creates a unified interface on various types and provides different implementation details for various types. Since Traits seized a concept, a associated selection set, it can be reused in similar contexts.

Is defined: A traits template is a template class, possibly explicitly specialized, that provides a uniform symbolic interface over a coherent set of design choices that vary from one type to another Traits template is a template class, is likely to be explicitly specialized. Template class, which provides a unified, symbolized interface for a series of design options made according to different types.

Traits as adapters: Traits for adaptors

The problem of the database is enough, now let's take a more general example - Smart Pointers.

Suppose you are designing a SmartPTR template class. For a Smart Pointer, the most wonderful thing is that they can automatically manage memory issues, while in other respects like a regular pointer. And less wonderful things is that their implementation code is not good to deal with, some C Smart Pointer implementation is simply in the dark. This brutal fact brings an important experience: you'd better do everything possible to die, write an excellent, industrial intensity Smart Pointer to meet all your needs. In addition, you usually can't modify a class to accommodate your Smart Pointer, so your SmartPtr must be flexible enough. There are many levels of hierarchy using reference counts and corresponding function management objects. However, there is no Reference Counting standard implementation method, and each C library supplier is different in grammar and / or semantics. For example, there is two interfaces in your application:

Most of the class realizes the refcounted interface: class refcks {public: void incref () = 0; bool decref () = 0; // if you decref () to zero // reference, the object iS destroyed // Automatic and Decref () RETURns True Virtual ~ Refcounted () {}};

The Widget class provided by a third party different interfaces: class Widget {public: void AddReference (); int RemoveReference (); // returns the remaining // number of references; it's the client's // responsibility to destroy the object. ..

However, you don't want to maintain two Smart Pointer classes, you want two classes to share a smartptr. A Traits-based solution Pack all two different interfaces and semantically unified interface, establish a general template for common classes, and establish a special version of Widget, as follows:

// EXAMPLE 6: Reference Counting Traits // Template Class RefcountingTraits {Static Void Refer (T * P) {P-> Incref (); // Assume RefCounted Interface} Static Void Unrefer (t * p) {P -> Decref (); // Assume refcounted interface}};

Template <> Class RefcountingTraits {static void refer (widget * p) {p-> addreference (); // use widget interface} static void unrefer (widget * p) {// use widget interface if (p-> RemoveReference () == 0) Delete P;}};

In SmartPtr, we like to use RefCountingTraits: template class SmartPtr {private: typedef RefCountingTraits RCTraits; T * pointee_; public: ... ~ SmartPtr () {RCTraits :: Unrefer (pointee_);} }

Of course, in the above example, you may argue that you can directly specialize the construction and destructive function of the Widget class's SmartPTR. You can use the template specialization technology in the smartptr it, instead of using the head in Trait, so that additional classes can be eliminated. Despite this problem, this idea is correct, but it is still a defect you need to pay:

[Translator provides an example for explicit specialization for SmartPTR itself for the SMARTPTR itself:] // General Type Template Class Smartptr {Private: T * Pointee_; Public: SmartPtr (T * Pobj ) {POINTEE_ = POBJ; POBJ-> incref ();} ... ~ smartptr () {Pointee_-> Decref ();}};

// widget class special version Template <> class smartptr "widget> {private: t * pointee_; public: smartptr (t * pobj) {POINTEE_ = Pobj; Pobj-> addReference ();} ... ~ smartptr () { IF (Pointee_-> RemoveReference () == 0) DELETE POINTEE_;}};

· This lacks scalability. Add a template parameter to the smartptr again, 喏, all over! You can't specialize such a SmartPtr , where the template parameter T is Widget, and u can be any other type. No, you can't do it. Incidentally, Smart Pointer is often used as template parameters. [Interpreter] That is, the IF-statement portion of the explicit template is specialized, providing a special specialized scheme for special template parameters, and must specify all parameter types when defined, and cannot be "partially specified". For example: // else-part, for all other types of Template Class Demo {...};

Template <> Class Demo {...}; // can, T and u are specified

Template <> Class Demo {...}; // can not only specify some template parameters

Template Class Demo {...}; // want to make a clearance? Can't help, C is not allowed]

· The final code is not so clear. Trait has a name, and organizes the relevant things well, so the use of Traits is more easily understood. In contrast, use the code that directly specializes in the SmartPtr member function, it looks more likely to like hackers.

· You can't use a variety of traits to one type.

With the solution of the inheritance mechanism, even if it is perfect, there is at least the above defects. Solve such a variant problem, use inheritance is too cumbersome. In addition, another classic mechanism that is usually used to replace the inheritance scheme --Containment, which is also used here to add the snake to add, cumbersome. Instead, the Traits scheme is clean and profound, simple and effective, and it is used in justice. An important application of Traits is "Interface Glue", generic, adaptable adaptive adapter. If the different classes have different implementations for a given concept, Traits can replace these implementations into a public interface.

For a given type, you provide a variety of traits: now, we assume that all people like your SmartPtr template class, until one day, in your multi-threaded app, the mysterious bug is now awakened. You find that the culprit is widget, and its reference count function is not a thread. Now you have to realize Widget :: addReference and widget :: removereference, the most reasonable location should be in RefcountingTraits, play a patch:

// Example 7: Patching Widget's traits for thread safety // template <> class RefCountingTraits {static void Refer (Widget * p) {Sentry s (lock_); // serialize access p-> AddReference ();} static Void unrefer (widget * p) {SENTRY S (LOCK_); // Serialize Access if (P-> RemoveReference () == 0) Delete P;} private: static lock lock_;

Unfortunately, although you are still running, you will run correctly after testing, but the program slows like a snail. After careful analysis, I found that the place you just made a bad bottleneck in the procedure. In fact, only a few widgets are needed to be visited by several threads, and most of the widgets are only accessible by one thread. What you have to do is to tell the compiler that uses multiple thread traits and single-threaded traits two different versions. Your code mainly uses single-threaded traits.

How to tell the compiler to use that traits? So doing this: Transfer traits to the other template parameters to SmartPtr. By default, vintage Traits templates are transmitted, and specific templates are instantiated with a specific type.

Template > Class SmartPtr {...};

You don't make changes to the single-line version of RefCounts , and put the multi-threaded version in a separate class:

Class MtRefCountingTraits {Static Void Refer (Widget * P) {SENTRY S (Lock_); // Serialize Access P-> AddReference ();} static void unrefer (widget * p) {SENTRY S (Lock_); // Serialize Access IF (P-> RemoveReference () == 0) delete p;} private: static lock lock_;}; now you can use SmartPtr for single-threaded purposes, use SmartPtr for multi-threaded purposes. This is! Just as Scott Meyers may say, "If you don't experience happiness, I don't know how to find fun."

If a type is cope with a trait, it is enough as long as it is specialized with explicit templates. Even if a type requires multiple trait to cope with us. Therefore, Traits must be able to plug in from the outside instead of "calculating" inside. One should be remembered is to provide a traits class as the last template parameter. The default Traits are given by the template parameters.

Definition: A Traits class (relatively relative to the Traits Template class) or an instance of a Traits template class, or a separate class that exhibits the same interface with the Traits Template class.

(Finish)

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

New Post(0)