Traits technology: If-else-then

Source: Internet
Author: User
Tags traits

What is traits? Why do people think of it as an important technology of C ++ generic programming?

In short, traits is so important because it allows the system to make decisions based on the Type during compilation,
It is like making a decision based on the value during running. Furthermore, this technology follows "adding an indirect layer"
To solve a lot of software engineering problems, traits enables you)
To make a choice. In this way, the final code becomes clear and easy to read and maintain. If you use traits correctly
Technology, you can get these benefits without paying any performance and security cost, or be able to fit with other
Solutions.

For example, traits is not only a core tool for generic programming, but also I hope the following examples can convince you,
It is also useful for very specific problems.

Suppose you are writing a relational database application. You may provide
Api library. But of course, soon you will feel you have to write something
Wrap functions to organize those original APIs. On the one hand, they are for simplicity, and on the other hand, they can better adapt
Task on your hand. This is the pleasure of life, isn't it?

A typical API is like this: provides a basic method to set cursor (a row set or
The raw data is transmitted to the memory. Now let's write an advanced function
Obtain the values of a column to avoid exposing the underlying details. This function may look like this:
(Assume that the database API starts with dB or db)

// Example 1: wrapping a raw cursor int fetch
// Operation.
// Fetch an integer from
// Cursor "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 <int> (temp );
}

All of us may have to write this kind of interface function at some time. It is hard to handle but very heavy.
Yes, it handles a lot of details, and this is just a simple example. Fetchintfield abstraction, which provides
A high-level function that can obtain an integer from the cursor without worrying about the complicated details.

Since this function is so useful, we certainly hope to reuse it as much as possible. But how? A very important general step is to enable this function to process types other than Int. To achieve this, we need to carefully consider the part of the Code related to the int type. But first, what do db_integer and db_integer mean? Where did they come from? In this case, relational database vendors usually provide some type-mapping helpers with the API, defining a symbolic constant or typedef for each type and simple structure supported by the relational database service, maps the database type to the C/C ++ type.

The following is a hypothetical 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 try to write a fetchdoublefield function as the first step towards generics. This function returns a double value from the cursor. The Type Mapping provided by the database itself is db_currency, but we want to operate it in double format. Fetchdoublefield looks very similar to fetchintfield, which is a twin brother. 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 ("cannot 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 single function for each type, so
If you can combine fetchintfield, fetchdoublefield, and other fetch functions in one place
As one.

Let's list the differences between the 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 does not seem to follow any rule, but is very casual. It is closely related to the type provided by the database supplier (exactly. The template mechanism itself is powerless and does not provide such an advanced type inference mechanism. We cannot organize different types with inheritance relationships because we are dealing with primitive types. Given the limitations of the API and the underlying features of the problem, it seems that we have no choice. But we still have a way to live.

Entering the traits door: The traits technology is used to solve the above problem: it combines code snippets related to various types and has the ability to be similar to and/or structures, then, different variants can be generated based on different types. Traits relies on the explicit template specialization mechanism to obtain such results.
This feature allows you to provide a separate implementation of the template class for each specific type, as shown in Example 3:

// Example 3: A traits example
//
Template <class T>
Class sometemplate
{
// Generic implementation (1)
...
};

// Pay attention to the following special syntax
Template <>
Class sometemplate <char>
{
// Implementation tuned for char (2)
...
};
...
Sometemplate <int> A; // will use (1)
Sometemplate <char *> B; // will use (1)
Sometemplate <char> C; // will use (2)

If you use the char type to instantiate the sometemplate class template, the compiler will use the explicit template declaration to make it special. For other types, the general template is used for instantiation. This is like a type-driven if-statement. Generally, the most common template (equivalent to the else part) is first defined, and if-statement is later. You can even decide not to provide a general template at all, so that only the specific instantiation is allowed, and others will cause compilation errors.

Now let's link this language feature with our questions. We need to implement a template function fetchfield and instantiate it using the type to be read as a parameter. Inside this function, I will use something called typeid to represent the symbolic constant. To obtain the int value, its value is db_integer, to obtain a double value, the value is db_currency. Otherwise, an error must be reported during compilation. Similarly, we also need to operate different api types (db_integer/db_currency) and different conversion algorithms (expressions/static_cast) based on the types to be obtained ).

Let's use an explicit template special mechanism to solve this problem. We need to have a fetchfield that can generate different variants for a template class, and that template class can be explicitly specialized for int and double. Each special feature must provide a uniform name for these variants.

// Example 4: defining dbtraits
//
// The most common situation of most general case not implemented is not implemented
Template <typename T> struct dbtraits;
// Specialization for int
Template <>
Struct dbtraits <int>
{
Enum {typeid = db_integer };
Typedef db_integer dbnativetype;
// Note that the following convert is static member function -- Translator
Static void convert (dbnativetype from, Int &)
{
To = static_cast <int> (from );
}
};
// Specialization for double
Template <>
Struct dbtraits <double>
{
Enum {typeid = db_currency };
Typedef db_currency dbnativetype;
// Note that the following convert is static member function -- Translator
Static void convert (const dbnativetype & from, double &)
{
To = from. integral_part + from. fractionary_part/100 .;
}
};

Now, if you write dbtraits <int>: typeid, you get db_integer.
Dbtraits <double >:: typeid. The result is db_currency.
Dbtraits <anything_else>: typeid. What is the result? Compile-time error!
Because the template class is declared and not defined.

Is it permanent? Let's take a look at how we can use dbtraits to implement fetchfield.
We put all the changed parts-Enumeration type, API type, and conversion algorithm-In dbtraits
Here, our function only contains the same part of fetchintfield and fetchdoublefield:

// Example 5: a generic, extensible fetchfield using dbtraits
//
Template <class T>
Void fetchfield (db_cursor & Cr, unsigned int Col, T & Val)
{
// Define the traits type
Typedef dbtraits <t> 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 ("cannot transfer data ");
Typename traits: dbnativetype temp;
Memcpy (& temp, Cr. column_data [col], sizeof (temp ));
Traits: Convert (temp, Val );
Db_release_column (& Cr, col );
}

Done! We just implemented and used a traits template class!

Traits depends on the explicit template to drag out the parts of the Code that change due to different types and use a unified interface for packaging. This interface can contain anything a C ++ class can contain: Embedded types, member functions, member variables, the template code as a customer can be indirectly accessed through the APIS exposed by the traits template class.

Such a traits interface is usually implicit, and the implicit interface is not as good as function signatures.
Strict, for example, although dbtraits <int >:: convert and dbtraits <double >:: convert have very low
Same signature, but they all work normally.

The traits template class establishes a unified interface for various types, and provides different implementation details for various types. Because traits captures a concept and an associated selection set, it can be reused in similar contexts.

Definition: 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.

A traits template is a template class, which is probably an explicit and special template class. It provides a unified and symbolic interface for a series of design choices based on different types.

Traits as adapters: The traits used as the adapter.

The database has enough questions. Now let's look at a more general example-smart pointers.

Suppose you are designing a smartptr template class. For a smart pointer, the most amazing thing is that they can automatically manage memory problems and look like a regular pointer in other ways. The less amazing thing is that their implementation code is not a good deal, and some C ++ smart pointer implementation techniques are simply a magic in the dark. This cruel fact has brought about an important practical experience: You 'd better do everything possible to write an outstanding, industrial-intensive smart pointer to meet all your needs. In addition, you usually cannot modify a class to adapt to your smart pointer, so your smartptr must be flexible enough.

Many classes use reference counting and corresponding functions to manage the lifetime of objects. However, there is no standard implementation method for reference counting. Every C ++ library vendor has different implementation syntaxes and/or semantics. For example, there are two interfaces in your application:

Most classes implement the refcounted interface:
Class refcounted
{
Public:
Void incref () = 0;
Bool decref () = 0; // If you decref () to zero
// References, the object is destroyed
// Automatically and decref () returns true
Virtual ~ Refcounted (){}
};

The widget class provided by a third party uses 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 do not want to maintain two smart pointer classes. You want the two types to share one smartptr. A traits-based solution wraps up two different interface terms and unified semantic interfaces, creates a general template for common classes, and creates a special version for widgets, as shown below:

// Example 6: reference counting traits
//
Template <class T>
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 <widget>
{
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 use refcountingtraits as follows:

Template <class T>
Class smartptr
{
PRIVATE:
Typedef refcountingtraits <t> rctraits;
T * pointee _;
Public:
...
~ Smartptr ()
{
Rctraits: unrefer (pointee _);
}
};

Of course, in the above example, you may argue that you can directly specialize in the smartptr constructor and destructor of the widget class. You can use the template specialization Technology in smartptr, instead of the trait header, to eliminate additional classes. Although this idea is correct for this question, it is still caused by some defects that you need to pay attention:

[For the sake of illustration, the translator provides an example of explicitly specialized smartptr itself for the author to criticize:]
// General Type Edition
Template <class T>
Class smartptr {
PRIVATE:
T * pointee _;
Public:
Smartptr (T * pobj ){
Pointee _ = pobj;
Pobj-> incref ();
}
...
~ Smartptr (){
Pointee _-> decref ();
}
};

// Widget version
Template <>
Class smartptr <widget> {
PRIVATE:
T * pointee _;
Public:
Smartptr (T * pobj ){
Pointee _ = pobj;
Pobj-> addreference ();
}
...
~ Smartptr (){
If (pointee _-> removereference () = 0)
Delete pointee _;
}
};

· Lack of scalability. If you add another template parameter to smartptr, complete is all done! You cannot specifically define a smartptr <T. U>, where the template parameter T is a widget, and u can be of any other type. No, you cannot. Additionally, smart pointer is often used as a template parameter.
[Explanation: in other words, the special if-Statement section of the explicit template provides a special scheme for special template parameters. All parameter types must be specified during definition, it cannot be "partially specified ". For example:
// Else-part, for all other types
Template <class T, Class U>
Class demo {...};

Template <>
Class demo <type1, type2> {...}; // Yes, T and U are specified

Template <>
Class demo <type3, u >{...}; // No, you cannot specify only some template parameters

Template <Class U>
Class demo <type4, u >{...}; // want to pass? Sorry, C ++ is not allowed]

· The final code is not so clear. Trait has a name and well organizes related things, so the code using traits is easier to understand. In contrast, the code that uses directly specialized smartptr member functions seems more attractive to hackers.

· You cannot use multiple traits for one type.

A solution using an inheritance mechanism, even if it is flawless, has at least the above defects. To solve such a variant problem, it is too cumbersome to use inheritance. In addition, containment, another classic mechanism used to replace the inheritance scheme, is also cumbersome to use here. On the contrary, the traits solution is neat, concise, and effective, and the combination of things is just right.

An important application of traits is "interface glue", which is universal and highly adaptable.
Adapter. If different classes have different implementations for a given concept, traits can organize these implementations into a public interface.

Multiple traits are provided for a given type: Now, let's assume that all people like your smartptr template class until one day, you start to discover a mysterious bug in your multi-threaded application, the dream was awakened. You find that the culprit is the widget. Its reference counting function is not thread-safe. Now you have to implement the Widget: addreference and Widget: removereference. The most reasonable position should be to apply a patch in refcountingtraits:

// Example 7: patching widget's traits for thread safety
//
Template <>
Class refcountingtraits <widget>
{
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 _;
};

Unfortunately, although you re-compile the program and run the program correctly after the test, the program is as slow as a snail bait. After careful analysis, we found that what you did just put a bad bottleneck into the program. In fact, only a few widgets need to be accessed by several threads, and the vast majority of the remaining widgets are accessed by only one thread. What you need to do is to tell the compiler to use different versions of multi-thread traits and single-thread traits as needed. Your code mainly uses single-thread traits.

How can I tell the compiler to use that traits? Do this: pass traits as another template parameter to smartptr. By default, the old-fashioned traits template is passed, and a specific template is instantiated with a specific type.

Template <class T, class rctraits = refcountingtraits <t>
Class smartptr
{
...
};

You do not modify the refcountingtraits <widget> of the single-threaded version, but place 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 _;
};

Now you can use smartptr <widget> for single-threaded purposes, and smartptr <widget, mtrefcountingtraits> for multithreading purposes. That's it! As Scott Meyers may say, "If you have never experienced happiness, you don't know how to find it ."

If a type can be handled by only one trait, it is enough to use an explicit template for specialization. Now, even if one type requires more than one trait to cope with it, we can make a decision. Therefore, traits must be able to be pushed from the outside, rather than being "computed" internally ".
A common practice to remember is to provide a traits class as the last template parameter. The default traits is specified by the default value of the template parameter.

Definition: A traits class (opposite to the traits template class), an instance of a traits template class, or a single traits template class that shows the same interface

Contact Us

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

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

A Free Trial That Lets You Build Big!

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

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

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

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