GURU of The Week Terms 28: "Fast PIMPL" technology

zhaozj2021-02-16  52

GotW # 28 "Fast PIMPL" technology (The "fast pimpl" iDiom)

Difficulty: 6/10

Temperans called "low-dependent" or "efficacy", which is quite tempting, but it is not always good ideas. There is a very wonderful way here to be able to achieve both objectively and safely.

problem

The overhead of standard Malloc () and new () calls is very large. In the following code, the programmer originally designed a member of the class X in the class y:

// file y.h

#include "x.h"

Class y {

/*...

X x_;

}

// file y.cpp

Y :: y () {}

This Class Y declaration requires CLASS X's stated (from x.h). To avoid this condition, the programmer is trying to write this:

// file y.h

Class X;

Class y {

/*...

X * px_;

}

// file y.cpp

#include "x.h"

Y :: y (): px_ (new x) {}

Y :: ~ y () {delete px_; px_ = 0;}

This is well hidden X, but it caused: When Y is widely used, the overhead on dynamic memory allocation reduces performance. In the end, our fearless programmers accidentally discovered the "perfect" solution: neither need to include x.h in Y.H, nor does it require dynamic memory allocation (even if you don't need it!):

// file y.h

Class y {

/*...

Static const size_t sizeofx = / * some value * /;

Char X_ [SizeOfx];

}

// file y.cpp

#include "x.h"

Y :: y () {

askERT (SIZEOFX> = SizeOf (x));

NEW (& x_ [0]) x;

}

Y :: ~ y () {

(ReinterPret_cast (& x_ [0])) -> ~ x ();

}

discuss

answer

Brief answer

Do not do this. Bottom line: C does not directly support Opaque Types, depending on the fragile attempt to this limit (some people even call this "HACK" [Note 1]).

The programmer is expected to be some things, it is called "fast pimpl" method, I will introduce "why I try 3 is a bad" section.

Why do you try 3 is bad?

First, let us think about why the trial 3 above is poor, from the following:

Align. Unlike the dynamic memory obtained from :: operator new (), this X_ character buffer does not ensure the alignment of type X. attempt

Let X_ work more reliable, the programmer needs to use a "max_align" union, its implementation is as follows:

Union max_align {

Short dummy0;

Long Dummy1;

Double Dummy2;

Long Double Dummy3;

Void * Dummy4;

/*... And Pointers to Functions, Pointers to

MEMBER FUNCTIONS, POINTERS to Member Data,

Pointers to classes, Eye of newt, ... * /

}

It needs to use this:

Union {max_align m;

Char X_ [SizeOfx];

}

This is not guaranteed to be fully portable, but in actual use, it is already perfect, only at very few systems (or may not be), it can't work.

I know some veteran approved even recommend this skill. With conscience, I still called Hack and strong against it.

2. Fragile. The author of Y must be extremely careful to handle other common functions of Y. For example, Y must not use the default assignment operation, it must be prohibited from providing a version.

Write a safe Y :: operator = () is not very difficult, I left it to the reader as an exercise. Remember to consider the needs of unusual security in this operation and Y :: ~ Y (). When you are finished, I think you will agree that the trouble made by this method is much serious than it brings.

3. Maintain the cost. When SizeOf (x) increases to more than SizeOfx, the programmer must increase SizeOfx. This is a maintenance burden that is not causing attention. Choosing a larger sizeOfx value can alleviate this burden, but exchange for effectiveness (see # 4).

4. Low performance. As long as SizeOfx is greater than SizeOf (X), space is wasted. This loss can be minimized, but it has caused maintenance burden (see # 3).

5. Stubborn. I put it in the end, but not the lightest: in short, obvious, programmers try to do something "unique". To be honest, in my experience, "Different" and "HACK" are almost agreewords. Whenever you see this "subvert" behavior - as this type of assignment object in the character array, or the explicit destructor is added to add Placement New with explicit descriptions, you Be sure to say "no".

I mean this. I reflect on it.

Better solution: "Fast PIMPL"

The motivation hidden X is to avoid Y's user must know (so depending on) X. In the C community, in order to eliminate this implementation, the "PIMPL" method is usually used, which is the method of our fearless programmer initially tried.

The only problem is that the "PIMPL" method causes performance to decrease in performance due to the free space allocation of memory. Typically, a special type of memory allocation performance problem is used to provide an overloaded version of an Operator New because the fixed-size memory splitter can be much higher than the general-purpose memory distributor.

Unfortunately, this also means Y's author must also be the author of x. Usually, this is not true. The real solution is to use an efficient PIMPL, that is, there is a PIMPL class that is Operator New.

// file y.h

Class YIMPL;

Class y {

/*...

YIMPL * PIMPL_;

}

// file y.cpp

#include "x.h"

Struct yimpl {// yes, 'struct' is allowed :-)

/ 9.private stuff here ... * /

Void * Operator new (size_t) {/*...*/}

Void Operator Delete (void *) {/*...*/}

}

Y :: y (): PIMPL_ (New YIMPL) {}

Y :: ~ y () {delete piMPL_; PIMPL_ = 0;}

"Ah!" You call, "We found the Holy Cup - FAST PIMPL!". Ok, yes, but first wait, think about how it works, and what it is. In the C book you collect, I definitely demonstrate how to implement an efficient fixed-size memory allocation / release function, so I don't have more Luo. What I want to discuss is "availability": a trick, put them in a common fixed-size memory distributor template, as follows:

Template

Class fixedallocator {

PUBLIC:

Void * allocate (/ * Requested Size is always s * /);

Void Deallocate (Void *);

Private:

/ 9.IMplement useing statics? ... * /

}

Because its private details are likely to use Static to implement, the problem is deallocate () whether it is called a Static object destructor (Because the private Details Are LIKELY TO Use Statics, HoWever, There Could Be Problems if deallocate () Is Ever Called from a static Object's dtor). Perhaps the safer method is to use the Single mode, use a separate list for each requested size, and use a unique object to manage all these linked lists (or, as a function of efficiency, use one for each size "bucket" Link list; for example, a linked list management size of 0 to 8 memory blocks, another chain list management size in memory blocks, etc.)

Class fixedallocator {

PUBLIC:

Static fixedallocator * instance ();

Void * allocate (SIZE_T);

Void Deallocate (Void *);

Private:

/ ......singleton importation, Typically

With easier-to-manage stats Than

The Templated Alternative ABOVE ... * /

}

Let us use a auxiliary base class to encapsulate these calls:

Struct fastpimpl {

Void * Operator new (size_t s) {

Return Fixedallocator :: Instance () -> Allocate (s);

}

Void Operator Delete (Void * P) {

Fixedallocator :: instance () -> deallocate (p);

}

}

Now, you can easily write your any Fast Pimpl:

// Want this One to be a fast pimpl?

// Easy, Then Just Inherit ...

Struct yimpl: fastpimpl {

/ 9.private stuff here ... * /

}

But, be careful!

Although this is very good, don't use Fast PIMPL. You got the best memory allocation, but I was expected to maintain these independent linkers will result in a decline in space efficiency, because this is more common than usual memory fragment (Managing Specte Free Lists for Objects of Specific Sizes Usually Means Incurring A Space Efficiency Penalty Because Any Free Space Is Fragment Across Several Lists. Like other performance optimization methods, this method is only used after using PROFILER profile performance and proves that it is necessary to optimize the performance.

(Note 1. I am not one of them. :-))

(Note 2. See gotw # 24.)

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

New Post(0)