BOOST source code analysis: generic pointer ANY 海 百 川 (REV # 2)

xiaoxiao2021-03-06  101

BOOST source code analysis: generic pointer ANY 海 百 川 (REV # 2)

Liu Weipeng

C Louvre (http://blog.9cbs.net/pongba)

motivation

C is a strong type language. All strong types of language are harsh, and the type is not complaining that you can't convert a type of type to a certain type. Of course, if you provide a conversion operation between the type The symbol or a certain degree of implicit conversion allowed by the standard (if the implicit conversion of a temporary variable through the non-Explicit constructor or the basic type between INT, long) is another matter. In general, in order to maintain type safety, C has severe requirements. However, sometimes the programmer may have this:

INT I;

Iong J;

X x; // hypothesize X for the user-defined class

Any anyval = i;

... // Use Anyval As a Int Value

Anyval = j;

... // Use anyval as a long value

Anyval = x;

... // Use anyval as a long value

Consider such a "generic pointer" how to design is a very interesting thing.

1. It itself can't be a template class, because if it is a template, you must provide template parameters for it. And in fact you don't want to do this. You want the same object to accept any type of data. This object is AnyVal in the code above. However, if you have to provide template parameters for it, then the above code looks like this:

Any AnyintVal = i;

Any Anylongval = j;

...

This obviously has lost an advantage of AnyVal - accepts all types of data in a single object. It's better to write directly to it:

INT AnyintVal = i;

INT Anylongval = J;

Therefore, ANY cannot be a template class.

2. It must provide some information about the type of object it saved.

3. It must provide some way to "take it out".

In fact, the Boost library has provided such a class boost :: Any, I will tell you the principles and constructs for you.

Boost :: Any principle and structure

First, you must provide a template constructor and template Operator = operator. Because you must allow users to write:

Any any_value (val); // VAL type is any

Any_Value = VAL1; // VAL1 type is also arbitrary

Such a code.

Second, the data storage is a problem. Data should be dynamically stored, that is, the container of the data is dynamically allocated to store the data, and the pointer to this container is stored in the ANY class, which is clearly indicated by the pointer to the base class of this container, because the container must be template And the pointer members in the ANY class must not be generic (because all the ANY can't be generic, all data members in anyne cannot be generic), so the conclusion is: preparing a non-flooding for the container Base class, let the pointer points to the base class.

Let's take a look at how the Boost library is specific to these two points.

Excerpted from "boost / ann.hpp"

Class Any

{

PUBLIC:

Class PlaceHolder // Pan-type data container Holder non-float base class

{

PUBLIC:

// The false prevention function, to ensure that the derived class object can use the base class

Virtual ~ placeholder () {}

PUBLIC:

/ / Provide information about type

Virtual const st :: type_info & type () const = 0; Virtual PlaceHolder * Clone () const = 0; // Copy

}; // PlaceHolder

Template

Class Holder: Public PlaceHolder

{

PUBLIC:

Holder (Const ValueType & Value)

: HELD (Value)

{}

PUBLIC:

Virtual const st :: type_info & type () const

{

// TypeId Returns the Std :: TypeInfo object reference, the latter contains the type information of any object, such as Name, and an Operator == operator You can use TypeId (OneObj) == TypeID (Anotherobj) to more than two objects Whether the type is consistent.

Return TypeId (ValyEType);

}

Virtual PlaceHolder * Clone () Const

{

Return New Holder (HELD); // Remove the virtual function, return your own replica

}

PUBLIC:

ValueType HELD; / / Data Saved Place

}; // Holder

// Pointer to the base class of the generic data container HOLDER

Placeholder * Content;

// Template constructor, dynamically allocate data container and call its constructor

Template

ANY (Const ValueType & Value)

: Content (New Holder (Value))

{}

...

/ / Is the same as the template constructor, but use SWAP idiom

Template

Any & Operator = (Const ValueType & RHS)

{

// Create a temporary object ANY (RHS), then call the following SWAP function for underlying data exchange, pay attention to the temporary object with * this exchange data, so the underlying data of RHS is not changed, just after the SWAP ends. The object has the underlying data of * this, while * this also has a copy of the data of the RHS owned when the temporary object constructed. The temporary object is then automatically destructed due to the end of the lifetime, * The original underlying data with the smoking.

ANY (RHS) .swap (* this);

RETURN * THIS;

}

Any & Swap (any & rhs) // swap function, exchange underlayer data

{

Std :: swap (content, rhs.content); // only simply divides the value of the two pointers

RETURN * THIS;

}

~ any () // destructor

{

// Release the container, use the base class pointer, which is the reason for PlaceHold needs a false prevention function.

DELETE Content;

}

...

}

This is not all source code, but all important ideas have been revealed. The remaining part is just some simple details, please refer to the original file of the Boost library.

"But wait!", You are eager to say: "You lost the type of information." Oh ... Indeed, when you assign a value, you will lose information about the type. Consider the code you may want to write below:

INT i = 10;

Boost :: Anyval = i;

INT j = anyval;

// error, in fact, you want to assign anyval to another INT type variable, which should be allowed in some way, but it is never provide the conversion operator in the Any class, because you don't know if you have anyval in advance. Bearing what type of variable, the conversion operator is not given. When the idea of ​​the conversion operator completely fails, we can only use certain "foreign" explicit conversion operations. Like Static_cast <>. Boost provides any_cast <>, so you can write this:

INT j = any_cast ;

In fact, the code of any_cast is like this:

Template

ValueType Any_cast (Const Any & Operand)

{

// Call any_cast to the version of the pointer.

Const valuePe * result = any_cast (& Operand);

// If the CAST fails, that is, the actual saved is not valueetype data, an exception is thrown.

IF (! result)

Throw Bad_Any_cast (); // Derived from std :: bad_cast

Return * Result;

}

And any_cast is the version of the pointer:

Template

ValueType * Any_cast (any * operand)

{

// This type of check is very important, followed by more detailed explanation

Return

Operand &&

(Operand-> Type () == TypeId (ValyEType))? // # 1

& stat_cast *> (operand-> content) -> HELD

: 0; // There is a down type conversion

}

These two Any_cast versions should be well understood. In addition, the type inspection of # 1 in the latter version is also necessary. If this check is not checked, consider the following code:

INT i = 10;

Boost :: Anyval = i;

// If there is no type check, this will be compiled and the running period will not be wrong, but the assignment to D will be very strange.

Double D = any_cast (anyval);

This will be compiled, and the running period is usually not wrong. Here I explain why this is.

Boost :: Anyval = i; actually pointing the AnyVal.Content pointer to a Holder object (please review the code above). Then ANY_CAST (Anyval) actually calls any_cast <> for the reload version of the pointer, passes the AnyVal address, which is turned to # 1, because the call is any_cast , so # 1 The code is instantiated by the compiler:

// #2

Static_cast *> (operand-> content) -> HELD

But before, OPERAND-> Content actually points to any :: Holder , so this static_cast is "illegal", however, it is: it can compile! The reason is very simple: Holder is a derived class of PlaceHold, and the type of Operand-> Content is Placeholder. The conversion from the base type pointer to the derived pointer is considered legal. But this is a big mistake because the type of expression # 2 will be derived as Double! Original Holder is only for INT HELD; member assigns a sizeof (int) byte memory, but now you want to use the INT type Held as a Double type, that is, use the sizeof (double) byte RAM. So this is equivalent to: int i = 10;

Double * Pd = (Double *) & i;

// Behavior is undefined, but usually will not be wrong, but hidden errors are more terrible, the value of Di's D is almost certainly not what you want.

Double D = * Pd;

Using TypeInfo allows us to discover this type when running does not match and promptly throw an exception. However, there is a violation of the intuitive thing to be compiled by the wrong code, and you can't stop it from compile, because Holder and Holder are the base class of PlaceHolder. So I can only expect that the programmers know what they are doing, or it will give him an abnormality.

Use Boost :: Any to implement Virtual Template member functions

If you know, there is no Virtual Template function in C . However, sometimes you have this need, Any can satisfy this need to some extent, for example,

Class Base

{

PUBLIC:

Virtual Void Accept (Boost :: Any Andata)

{

...

}

}

Class Derived: Public Base

{

PUBLIC:

Virtual Void Accept (Boost :: Any Andata)

{

...

}

}

Such an Accept function can accept any type of data and is a Virtual function.

Directory (expand "Boost Source Analysis" series of articles)

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

New Post(0)