More Effective C ++ Terms 26 (on)

zhaozj2021-02-08  327

Terms 26: Limit the number of objects that can be generated (on)

You are obsessed with the object, but sometimes you want to be bound to your crazy. For example, there is only one printer in the system, so you want to use some way to limit the number of printer objects as one. Or you only get 16 distributive file descriptors, so you should ensure that the number of file descriptor objects cannot exceed 16. How can you do this? How to limit the number of objects?

If this is a proof of mathematics induction, you will prove from n = 1 and then derive other proofs from this. Fortunately, this is neither a certificate is not an induction. And starting from n = 0 more inspiration, so we start here. How can you completely block the object instantiation (INSTANTIATE)?

Allow zero or an object

Every time you instantiate an object, we know very clearly: "A constructor will be called." The fact is true that it is the easiest way to block the establishment of a class object, and the easiest way is to declare the constructor of the class. Private field of the class:

Class cantbeinstantiated {

Private:

Cantbeinstantiated ();

Cantbeinstantiated (Const Cantbeinstantiated &);

...

;

After doing so, everyone has no power to establish an object, and we can selectively relax this restriction. For example, if you want to build a class for the printer, you should comply with us only an object-available constraint, we should package the printer object in a function so that everyone can access the printer, but only one printer object is established. :

Class PrintJob; // forward declaration

/ / See Effective C Terms 34

Class printer {

PUBLIC:

Void SubmitJob (Const PrintJob & Job);

Void reset ();

Void Performselftest ();

...

Friend Printer & Theprinter ();

Private:

PRINTER ();

PRINTER (Const Printer & RHS);

...

}

Printer & theprinter ()

{

Static Printer P; // A single printer object

Return P;

}

This design consists of three parts, first, the constructor of the Printer class is private. Such a product can prevent the establishment of an object. Second, the global function Theprinter is declared as a class friend, allowing theprinter to avoid restrictions caused by private construction functions. Finally, Theprinter contains a static Printer object, which means that only one object is established.

Whenever client code is accessible to the system's printer, it has to use theprinter function:

Class printjob {

PUBLIC:

PRINTJOB (Const string & whattoprint);

...

}

String buffer;

... // Fill buffer

Theprinter (). RESET ();

Theprinter (). SubmitJob (Buffer);

Of course, you feel that theprinter uses global namespaces that are completely redundant. "Yes", you will say, "The global function looks like global variables, but the global variable is gauche (I don't know how to translate the translator's note), I want to put all the functions related to printing in the Printer class." I never dare to argue with people who use words like Gauche (some don't understand what the translator here), this is simple, as long as theprinter is static function in the PriHter class, then put it on you want Put the position. No longer need a friend declaration. Use a static function as shown below: Class Printer {

PUBLIC:

Static Printer & Theprinter ();

...

Private:

PRINTER ();

PRINTER (Const Printer & RHS);

...

}

Printer & Printer :: theprinter ()

{

Static Printer P;

Return P;

}

When the client uses Printer, some are cumbersome:

Printer :: theprinter (). RESET ();

Printer :: theprinter (). SubmitJob (buffer);

Another method is to remove the Printer out of the global domain, put it in Namespace (see Effective C Terms 28). The namespace is C a newer feature. Any can declare in the naming space in the global domain statement. Includes class, structure, function, variable, object, typedef, etc. Putting them in naming space does not affect their behavior characteristics, but it is possible to prevent naming conflicts in different namespaces. Put the Printer class and theprinter function in a namespace, we don't have to worry that others will also use the Printer and theprinter name; namespaces can prevent naming conflicts.

Namespaces are seen from syntactics, but it doesn't have public, protected, or private domains. All are public. As shown below, we put the Printer, Theprinter in the namespace called Printingstuff:

Namespace printingstuff {

Class Printer {// Namespace

Public: // PrintingStuff

Void SubmitJob (Const PrintJob & Job);

Void reset ();

Void Performselftest ();

...

Friend Printer & Theprinter ();

Private:

PRINTER ();

PRINTER (Const Printer & RHS);

...

}

Printer & theprinter () // This function is also in the namespace

{

Static Printer P;

Return P;

}

}

// Name the space to end

After using this namespace, the client can use the Fully-Qualified Name (ie, including the name of the namespace).

PrintingsTuff ::...

PrintingsTuff ::.......................

But you can also use the USING statement to simplify the keyboard input:

Using printingstuff :: theprinter; // From the namespace "printingstuff" // introduced the name "theprinter"

/ / Make it a current domain

Theprinter (). reset (); // can now be used to use local naming

Theprinter (). SubmitJob (Buffer); //, using theprinter

There are two subtle uncomfortable places on the implementation of Theprinter, it is worth seeing. First, a separate PRITNER is a static member in a function rather than static members in the class, which is very important. A static object in the class is actually always constructed (and release), even if the object is not used. In contrast, only the statically objects in the function are established when the function is executed, so if the function is not called, the object will not be established. (However, you have to pay for this, you have to check if you need to build an object every time you call the function.) Establishing a C a theoretical pillar is not required for what you don't have to use, in the function, put similar to Printer like Printer Object definitions are static members to persist in this theory. You should adhere to this theory as much as possible.

Compared with a static member of a function, there is still a shortcoming of static members in the class in a static member of a function, and its initialization time is uncertain. We can accurately know when the function's static member is initialized: "When performing a function of defining a static member for the first time." There is no time to be initialized without defining a static member of a class. C provides some guarantee in the initialization order of static members within a translation unit (which is generated by the source of an Object file), but there is no such guarantee for the initialization order of static members in different translation units (see Effective C Terms 47). In actual use, this will bring us a lot of trouble. When the statics of the function can meet our needs, we can avoid these troubles. In the example here, since it can meet the needs, why don't we use it?

The second subtle is the relationship between the inline and the static object in the function. Look at the form of theprinter's non-member function:

Printer & theprinter ()

{

Static Printer P;

Return P;

}

In addition to the first execution of this function (that is, constructing P, this is a row function - it consists of "return p;" a statement. This function is best for use as an inline function. However, it cannot be declared as inline. why? Please think about it, why do you want to declare an object as static? Usually because you only want a copy of the object. What does "inline" now think now? Conceptually, it means that the compiler uses a function to replace each call of the function, except for non-member functions. Non-member functions have other meaning. It also means INTERNAL LINKAGE.

Normally, you don't need to understand this language that is confused, you just need to remember one thing: "Functions with internal links may be copied in the program (that is, the program's goal (Object) The code may contain a code for more than one internal link function), which includes a static object in a function. "What is the result? If you create a non-member function containing local static objects, you may make the copy of the static object of the program more than one! So don't build non-member functions that contain local static data.

But you may think that the function is set to return a reference to a hidden object, which is wrong. Maybe you think it only needs to simply calculate the number of objects, once it takes too many objects, you will throw an exception, so that you will be better. As shown below, build a Printer object,: Class Printer {

PUBLIC:

Class toomanyObjects {}; // When the object is too much

// Use this anomaly class

PRINTER ();

~ Printer ();

...

Private:

Static size_t numoBjects;

Printer (constprinter & rhs); // can only have a Printer,

// So no copy

}; // (See Effective C Terms 27)

The core idea of ​​this method is to use NumObjects to track the number of PRITner objects. When constructing, its value is increased, and the value is reduced when it is released. If you try to construct excessive Printer objects, you will throw an exception to the TOMAYObjects type:

// Obligatory definition of the class static

SIZE_T Printer :: NumObjects = 0;

Printer :: printer ()

{

IF (NumObjects> = 1) {

Throw toomanyObjects ();

}

Continue to run the normal constructor;

NumObjects;

}

Printer :: ~ printer ()

{

Perform normal destructor;

--NumObjects;

}

This method of restricting the number of subjects has two advantages of more than attractive. One is it intuitive, everyone can understand it. Another is easy to promote its use, allowing the number of most objects to be established is not one, but a number greater than one.

Establish an environment

This method also has a problem. Suppose we have a special printer, is a color printer. This printer class has many places as the ordinary printer class, so we inherited from ordinary printing:

Class colorprinter: public printer {

...

}

Now suppose we have a normal printer and a color printer:

PRINTER P;

ColorPrinter CP;

How many PRITNER objects do this two definitions? The answer is two: one is P, one is CP. At runtime, when constructing the base class portion of the CP, ToomAnyObjects will throw the toomanyObjects. For many programmers, this is not what they expect. (Avoiding the CONCRETE class from other Concrete classes while designing, this problem will not be encountered. This design idea is detailed in terms 33).

When other objects contain the Printer object, the same problem occurs:

Class cpfmachine {// A machine, you can copy, print

Private: // Send a fax.

Printer P; // has printing capabilities

Faxmachine f; // has fax capabilities

CopyMachine C; // Copying capacity ...

}

Cpfmachine m1; // run normal

Cpfmachine M2; // Throw toomanyObjects unusual

The problem is that the Printer object can exist in three different environments: only them itself; as a base class of other derived classes; embedded in larger objects. These different environments have greatly confused the meaning of tracking "the number of objects", because the meaning of the "object existence" in your mind is inconsistent with the compiler.

Usually you only be interested in the presence of the object itself, you want to limit the amount of this instance. If you use the original Printer class example, it is easy to make this limit, because the Printer constructor is private, (there is no Friend declaration) Class with the Private constructor cannot be used as a base class, or it is not possible to embed other Object.

You can't derive new categories from classes with Private constructors, which leads to generating a general method of preventing derived classes, which does not need to be used with the number of object instances. For example, you have a class FSA that represents a Finite State Automata (a finite state). (This machine can be used in many environments, such as user interface design), and assume that you allow any number of objects, but you want to ban new categories from FSA. (One reason why this is to show that there is a non-false argument function in FSA. Effective C Terms 14 explains why the base class usually requires a virtual destructor, and the book terms 24 explains why there is no virtual function. The class is small.) As shown below, this design FSA can satisfy your two needs:

Class fsa {

PUBLIC:

// pseudogenic function

Static FSA * Makefsa ();

Static FSA * Makefsa (Const FSA & RHS);

...

Private:

FSA ();

FSA (Const FSA & RHS);

...

}

Fsa * fsa :: makefsa ()

{Return new fsa ();

FSA * FSA :: Makefsa (Const FSA & RHS)

{RETURN New FSA (RHS);

Unlike theprinter function always returns a reference to an object (referenced object is fixed), each Makefsa's pseudo-creation function is a pointer to a pointing object (the object to which is unique, different). That is to say, the number of FSA objects that are allowed to be established is not limited.

Well, but each pseudo-constructor is called new. This fact implies that the caller must remember to call DELETE. Otherwise resource leaks will occur. If the caller wants to exit the living space, the delete will be called automatically, he can store the pointer returned by Makefsa in Auto_PTR (see Terms 9); When they exit the living space, this object can automatically delete their pointing. Object:

/ / Indirect call default FSA constructor

Auto_PTR PFSA1 (fsa :: makefsa ());

// Indirectly Call FSA COPY CONSTRUCTOR

Auto_PTR PFSA2 (FSA :: Makefsa (* PFSA1));

... // Use PFSA1 and PFSA2, / / ​​just to delete them without worrying.

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

New Post(0)