Wide <Programming>: DISCRIMINATED Unions (3)

zhaozj2021-02-16  70

Wide : Discrriminated Unions (3) Andrei AlexandRescu

Before entering today's theme, there are some news you may be interested in news. Not long ago, Jonathan, H, Lundquist and Mat Marcus were compatible with Visual C 6 each other. Their implementation is an experiment on a certain idea and has not yet achieved a perfect point. In general, due to various compilers, in the present, Loki's role is the source of people's inspiration, rather than as a "packaged, retractable" product can be directly placed in your program. . Typical Loki users are a brave developer. He will not hide the table under the wrong message - even when the error message is enough to allow the network server "buffer overflow". However, this situation is changing when you read this article. When Rani Sharoni wrote to me a complete Loki for Visual C 7.0, I was not surprised. In addition to several places, the syntax used by the library remains unchanged, which is obvious because Visual C 7.0 (Visual C . Net) does not support partial template. If you use a compiler, maybe you also want to put the Loki to the platform you work [1]. (Translation: This article was published in August 2002. The current Loki is already compatible with VC6, VC7 and BCBs) Other related news - now on adding Loki to Boost, this move can be done Work is concentrated. Loki :: smartptr Submit work is already in the hands; just with a lot of discussion with a smart pointer, it also brings an unprecedented work efficiency. at the same time. Joe Swatosh has already submitted a very popular ScopeGuard [2], which is actually done by Petru Marinean and everyone will be included in Boost. In short, it is now the era of C rapid development. In the end, this means you can spend less strength and put more energy into a high-level design.

Variant: The work to be completed is in the first two parts of "return programming", we define the core part of the Variant class that can be identified and defined by the Variant class. This core class allows users to store an object in Variant by providing the main basic capabilities, and obtains that object in the object type in Variant, and the type of security. In addition, Variant is universal and very efficient. But these features are not enough to be excited. Of course, compatibility is good (but if you use old, incompatible compilers, what is used in light Variant's common use?) The same, it is definitely no harm. But light is not enough - when you write code for it, Variant's versatility and high efficiency cannot provide more features. The focus is, more features will be very helpful. Today we focus on adding some powerful features for Variant - some have never appeared in a similar implementation. But before this, let's talk about a few questions.

In the end, this still happened - and it will still happen again in life. No matter how you work, you can't please everyone. This is why, since "Modern C Design" has a bookstope of the bookstore. I have been waiting for comments similar to "your book", there is no doubt, or I can wait early or later. The sword of Dhakeley is a year in my head, and it is ultimately a book review on Amazon. falling down. In the comment, Modern C Design is "typical unrealistic, fake inner line, too theoretical things, is the people who do not have practical work experience and must be written for C Report every month watch". This sentence contains me - as author, and yourself, because you are looking at. Maybe we should stop right away. But maybe there is no need to stop. I have wanted to see the "Your Book" comment on the actual content; this comment is too easy to refute. The first thing, this book is based entirely on the author's C actual work every day - and puts many of the concepts in the actual work in the book. The second thing - this is why I want to organize all things - I really didn't feel any pressure after I wrote a "generic " part every two months. But there is something here I need to ask for your opinion. It may make the book review author troubles, I decided to write the second book, temporarily named "The Return Of C " (if you also put Herb Sutter and my new book "C Coding Standards", then The author really wants to be depressed). "The Return Of C " is not the sequel of "Modern C Design", and the sequel may not be high, especially when the sequel is just the idea of ​​re-facery repeating, but lacks some new inspiration and ideas. At the same time, I don't want this new book to be a collection of my own article. I believe that if you pay this book, you should read something I can't find online. This ultimately means free to post new ideas and interests. When you think of some cool ideas, the difficult decision is that this should be part of a new article or a part of the new book? This prevents me to write some exciting things, such as the generic implementation, composite object, or complete C error handling of the contract design, Observer mode. If you have any ideas for solving this conflict, don't hesitate, write to me. Fortunately, there is always a lot of cool new ideas suitable for the form of an article. For example, you can soon see why existing universal sorting and lookups are not perfect, how to get better efficiency than most libraries. Take only 80% of the time when you look up with that program (when you don't need I / O), that is, if you don't have to I / O, you can get 0.8 benefits from that article. Some people will let some people say: "Ah! This person has no shortage, so in that words" fact is, excellent long-term programming column, like Al Stevens, Michael Swaine, or our dear Bobby Schmidt, there are also external words in their columns. do you know? I love to read these. Just like reading Russian novels: I can't make it very eye-catching, but I love reading. Go back to Variant We use a few code instances to quickly recall which step is what is going on now. Variant objects can only store an object belonging to a type collection. With the template constructor, you can initialize Variant with a type of object in those types. E.g:

// Database field, can include a string, a whole, or a double-precision floating point type typef variant :: type> databasefield; // construct three database field objects - a character String one integer and a double-precision floating point DatabaseField FLD1 ("Hello, World"); DatabaseField FLD2 (25); DatabaseField FLD3 (3.14); With a Variant object, you can get its type:

Assert (fld2.typeid () == TypeId (int));

You can access the actual type of type in Variant:

String * p fld1.getptr (); // We know that FLD1 contains a string Assert (p! = 0); // Now it is "Hello, World!"

Variant has a traditional constructor and performs cleanup when calling a destructive function.

// Default constructor, initialize the default value - // to construct the object DatabaseField Fld5; assert (fld5.gettype () == typeid (String)); assrt (Fld5.getptr); String> () -> EMPTY ()); DatabaseField FLD4 = FLD1; // Copy Construction Assert (FLD4.GETTYPE () == TypeId (String)); Assert (* fld4.getptr "> () ==" Hello , World! ");

Ok, now we will implement some important functional functions. First, we solve the value of the type of VARIANT. You also see it, getPtr is good, but it is not enough to provide sufficient features based on its method. check it out:

Void Processfield (const datafield & fld) {if (const string * ps = fld.getptr ()) {...} else if (const * pi = fld.getptr ()) {... } Else if (const Double * pd = fld.getptr ()) {...}} You want to do anything with Variant, you must explicitly test each type that it may contain and operates each type separately . This is difficult to add program size - small if-else chain will soon burial out the actual work you do. Worse, when you add new types in your Variant (, in the DatabaseField definition, add the MEMO type) compiler, you will not pat your shoulders, remind you to add a new in every ELSE-IF test chain. The corresponding else-if, you have to do your own, the compiler is like an mob - you'd better let it stand on this side.

Visitation How do we implement these functions without using Variants to perform disgusting types? A solution is to add more functions in a Variant's analog VTABLE. , Just like TypeID, Clone, etc. This can be achieved, but it will destroy the characteristics of Variant's and the application. What we want is universal, scalable variant, not something to modify each time. You will not want to inherit the variant; it is a value of a value, not a reference type (Reference Type). Access is a more suitable technology here. Visitor [4] is a design pattern that allows you to add a new action in a class level without modifying this class level. How does access work? Complete explanation exceeds the scope of this article, but if you are not familiar, it is highly recommended that you look at visitors in [4]. Visitor model is applicable in many interesting places, and at least let you see that some types of levels do not have access to accessibility is a serious design error [5]. In a Variant case, the class level is a collection of stored types. They did not form a class level, but our existing simulated vTable allows us to define polymorphisms based on types, and these types themselves. (They can even be a basic type, as shown in the code instance above.) By the way, if you are interested in this topic, but don't understand "simulation vtable", don't forget this article based on the first two [ 6,7]. In order to let Variant can be accessed, we need to perform the following steps: * Define a Basevisitor class for the Variant class. In the case of DatabaseField, the basevisitor class is like this: struct basevisitor {Virtual void visit (string &) = 0; Virtual Void ViIT (INT &) = 0; Virtual Void Visit (Double &) = 0;}; * In analog virtual function table Define the Accept function in the implementation, just in the previous article [7] ... in Variant ... Template struct vtableImpl {... and the same ... static void accept Variant & var, basevisitor & v) {t & data = reinterpret_cast (& var.buffer_ [0]); v.visit (data);}}; * Add a pointer to the ACCEPT function in analog virtual function table structure : ... in Variant ... struct vTable {... and the previous one ... void (* accept _) (Variant &, Basevisitor &);}; * Don't forget to bind the Accept_ pointer in the Variant constructor Go to the VTableImpl :: Accept function.

... in Variant ... Template Variant (Const T & Val) {New (& Buffer_ [0] T (VAL) static vtable vtbl = {& vtableImpl :: TypeId, & VTableImpl :: Destroy, & VTableImpl :: Clone, & VTableImpl :: Accept, // New}; * PTR_ = & vtbl;} * Provide an interface function ... in Variant ... Void Accept (Basevisitor & V) {(Vptr _-> accept _) (* this, v), indeed, more work more than a single new function, but fortunately, we don't need to do this over and over again. Once the visit is ready, we can Operating Databasefield objects by simply adding new classes from Basevisitor: struct processDatabasefield public basevisitor {virtual void viRTUAL VOID VIT (INT & I) {...} Virtual Void Visit (Double & D) {...}}; DatabaseField Fld = ...; processDatabasefield processor; // Call the corresponding Visit () Fld.Accept (Processor) in the processor;

This brings us two benefits. First, you can now spread the member function of ProcessDatabaseField into each compilation unit (may specifically process string operations or digital operations separately). Second, if you add a new type to the definition of DatabaseField, for example: Typedef Variant :: type> databasefield; then add Void Visit in the Basevisitor's definition ( Memo &) = 0, then the compiler reminds each basevisitor inheritance class without proper processing of Memo; if you understand this article (I hope you can understand), you may think: "Great, this concept I understand . Access is a beautiful alternative to a getting angry getPtr. "Of course, just like any concept, it always has the actual details. The previous expression is aware of one thing, and some details are ignored. If you can see enough, you may notice such a disturbing details: Basevisitor can only process string, int, and double, it works well when accessing the DatabaseField object (DatabaseField can store any one in these types ), But useless to other Variant instances, because other Variant instances may contain different types of types. Then what should you define a virtual base classvisitor, a pure virtual function Visit (X &) corresponding to each type of Type X? This is fully implemented, and MODERN C Design shows how to do this. Better, Loki defines an access framework. Since this frame is not only about access, he is universal, hehe, why don't you use it? Let's see how to use [8]:

Template :: results> Class Variant {typedef loki :: visitor strictvisitpr;}; this is simple. In order to add new features in DatabaseField, now you need to do it from the DatabaseField :: Strictvisitor inherits the new access class. There is no doubt that DatabaseField :: Strictvisitor defines three pure virtual access functions to accept a string, an int and a Double parameter. Or any type you think should be included in DatabaseField. Great!

Access adjustments are now undergoing basic concepts, we can do some improvements. For example, if the access and constant correctness (const-correctness) is mixed. But Basevisitor :: Visit function is a very square parameter. This means that the following code cannot work:

Struct Street :: strictvisitor {virtual void visit (string & s) {cout << s;} Virtual Void Visit (int & i) {cout << i;} Virtual Void Visit (Double & D) {cout << D; }}; const DatabaseField FLD (4.5); // As a constant streamer str; // error! There is no overloading streamer :: Visit version // accepts const double & fld.accept (str); from the question return, we find the root of trouble. ... in Variant ... Template struct vtableImpl {... and the previous one ... static void accept (variant & var, basevisitor & v) {...}};

The above Accept function accepts a very quite Variant object as the first parameter. Bright words (I believe it is an ancient Greeks): "When a vTableImpl :: accept is useless, maybe two will be useful." In fact, what we have to do is to define a new Variant member function (The name is also called accept), but this time we have passed into a Const Variant & instead of a simple Variant &. The visitor base class corresponding to the Const Variant object can be obtained directly through the Loki :: Visitor, but it is transmitted to it a shaped type string. This type of string deformation accepts the original type string to return a new type string, each type of new type string has added const. Just like this:

template struct MakeConst; template <> struct MakeConst {// stop the type is not const typedef null_typelist Result;}; template struct MakeConst > {typedef TypeName Makeconst :: Result NewTail; TypedEf Typelist Result;

The key to MakeConst is that the HEAD type is converted to Const Head when returning the Result type. With Makeconst, we define a new visitorship class, just like this: Template :: result> class variant {typedef loki :: visitor strictVisitor; typedef Loki :: Visitor :: Result> ConststriSitor; ...};

So until now, how can the STRICT prefix in front of all the visitor names we define? It corresponds to strict access, there is any "non-strict" access. Consider the following requirements: if a DatabaseField is a string, it needs to be placed in square brackets [like this]. Not a string, no processing. The implementation is:

Struct Transformer :: strictvisitor {virtual void visit (string & s) {s = '[' s ']';} Virtual Void Visit (INT & I)} Virtual Void Viisit (Double & D) {// does not work}}};

In this case, although Transformer does not need to access IN and Double, it still needs to define empty Visit (INT &) and Visit (Double &) overload functions. The visitor implementation is strict: all functions must be implemented, the compiler ensures this. For the situation like Transformer, when we only need the subset of type collections, non-strict access is a method to achieve the purpose. In this way, Variant adds two types definitions: a non-harder access to the Variant object of very quantity and constant.

Template :: Result> Class Variant {typedef Loki :: Visitor StrictVisitor; typedef Loki :: Visitor :: Result> ConstStrictVisitor; Typedef loki :: nonstrictvisitor nonstrictvisitor; typedef loki :: nonstrictvisitor :: result constnstrstrictvisitor; ...};

Further improvement in the access mechanism for Variant may include a custom return type. (Now all access functions returns VOID)

The loose end is here to summarize our Variant implementation. Once you have access, you can add any features you want to increase in Variant, call the cost as the price (instead). This is to make Variant's consideration. Fortunately, this solution can expand vertically. (The overhead does not increase as the type of string of Variant is increased). The important feature of the available Variant implementation is to convert its value to another, or change the storage type in place. For example, consider the following code: DatabaseField FLD (45); assert (fld typeid () == typeid (int)); double d = fld.convertto (); assert (d == 45.0);

If it is now stored in the type of Variant object (which is int) can be converted to the required type (in this example is a double), the conversion is performed. Otherwise, ConvertTo throws an accident. One of the above changes is the type of FLD CHANGETYPE (); assert (fld typeid (); assert (* fld.getptr () == 45.0);

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

New Post(0)