C ++ from scratch (11) middle article - related knowledge of class

zhaozj2021-02-16  84

C from scratch (11) middle article - Class's related knowledge This article has a serious error on the discussion of virtual functions, and this error has been explained in this paper, and it is sorry for this. Due to the space limit, this article is "C from zero (11)", explains the implementation of multi-inheritance, virtual succession and virtual functions. Multiple inheritance has an interesting problem here, as follows: Struct a {long A, b, c; char d;}; struct B: public a {long e, f;}; above B :: E and B:: What is the shift of the mapping? Different compilers have different mapping results, and C does not have an forced specification for derived implementation. Most compilers are all the offset values ​​of B:: E maps to 16 (i.e., the length of A, with respect to the length of the custom type "C from zero (nine)"), b :: F mapping 20. This is equivalent to leaving the space to arrange the member variables of the parent class, and then arrange their own member variables. But there is such a semantic-tomato is a fruit and fruit, and the whale is marine organism is a duct animal. That is, an instance is both this type and that type, for this, C provides multiple derived or multi-inheritance, use "," interval each parent class, as follows: struct a {long A_a, A_B, C; Void ABC ( );}; struct b {long C, b_b, b_a; void abc ();}; struct ab: public a, public b {long ab, c; void abcd ();}; void A :: abc () { A_A = a_b = 10; c = 20;} VOID B :: ABC () {b_a = b_b = 20; c = 10;} void ab :: abcd () {A_A = B_A = 1; A_B = B_B = 2; C = a :: c = b :: c = 3;} void main () {ab ab; ab.a_a = 3; ab.b_b = 4; ab.abc ();} The above structure AB is from the structure A and The structure B is derived, ie we can say that AB is an example of B is also an instance of B, and is an example of AB. So when you are derived, a few mappings will be generated? According to the previous article, in addition to the AB type definition "{}" defined in "{}" (type long AB: :), also generate inheritance map elements, each mapping The modification of the element name is replaced with AB ::, the type is constant, the value of the map is not changed. Therefore, for two parent classes, 8 mappings are generated (4 mapping elements for each class), such as one of the names AB :: A_B, type long A ::, the value of the map is 4; A name is AB :: B_B, the type of long b ::, the value of the map is still 4. Note A :: ABC and B :: ABC's name, so the names of two mapping elements are AB :: ABC, but the type is a void (a ::) () one for Void (b ::) (), The address of the map is A :: ABC and B :: ABC, respectively.

Similarly, there are three names of the three map elements to be AB :: C, which are long A ::, long b :: and long AB ::, the offset value of: 8, 0 and 28, respectively, the mapping. Take the previously arranging the member variable of the parent class to the member variables of the subclass, so the value of the type long AB :: AB :: C map is the length of the two parent classes and add AB :: Ab The offset brought. Note that there is two pairs of the same name in the 8 mapping elements that inherited above, but there is no problem, because their types are different, and the final compiler will modify their names according to their respective types to form symbols, so connected There will be no problem, but there will be other problems. Ab.abc (); must be ab.ab :: abc (); because AB is AB type, but now there are two AB: ABCs, therefore directly writing ab.abc will report an error because Know which AB:, what should I do? Recall the public, protected, private inheritance mentioned in this article, which said that the public means that the outside world can treat the examples of the subclass as an example of the parent class. That is, all where you need to use the parent class instance, if it is a sub-class instance, and the relationship between them is a public inheritance, the compiler will convert the sub-class instance into a parent class instance. Therefore, Ab.a_a = 3 is actually ab.ab :: A_A = 3;, the type of AB :: A_A is long A ::, and the member operator is the same as the type belonging, the left type is AB, And the subclass of AB is A, so the compiler will automatically perform implicit type conversion, becoming the instance of AB into A, and then calculate the member operator. Note that the offset value of AB:: A_B and AB :: b_b is 4, then ab.a_b = 3; isn't it equivalent to ab.b_b = 3;? Even according to the above say, since the types of AB:: A_B and AB :: B_B are long A :: and long b ::, the former is only the former converted to a instance of A, converted to B, AB :: The offset of A_B and AB :: B_B map is still not changed. Therefore, it is changed to the number of the left side of the member operator. For structural AB, it is assumed that the member variable of the parent class A will rearrange the member variable of the parent class B, then the offset of the AB :: B_b is 16 (the length of the structural A plus B: C) ), But it is actually mapped to 4, so adds 12 (lengths of structure A) on the number of address types on the left side of the member operator. For AB:: A_B, since the member variables of the structure A are first arranged, only 0. only zero. Assuming the address corresponding to the above AB is 3000, for AB.B_B = 4; the number 3000 of the AB type address type is ".", Transfer to the number of the number B-type address type (because of the offset 12), Then, "." The number 4 of the offset type, plus 3012, and finally return the number 3016 of the type of LONG, and then continue to calculate "=". You can also know that the member operator in AB.a_a = 3; the number 3000 of the address type of the LONG type is returned, and ab.a_b will return 3004, and ab.ab will return 3024. Similarly, this will also make implicit type conversion LONG AB :: * p = & ab :: b_b; Note that the type of AB :: B_B is long b ::, will make implicit type conversion. How to convert? The original AB :: B_B mapping is 4, and now it will become 12 4 = 16, so that Ab. * P = 10;

At this time, I went back to the question just mentioned, AB :: ABC can't distinguish, what should I do? Note that there is also mapping elements A :: ABC and B :: ABC (two AB :: ABC are caused by two), so it can be written ab.a :: abc (); to indicate that the call is mapped to A :: ABC function. The type of A :: ABC here is VOID (A ::) (), and AB is AB, so the implicit type conversion, the above has no syntax problem (although A :: ABC is not a member of the structure AB, but It is a member of the parent class, C allows this situation, that is, the name A :: ABC is also used as part of the type match. If the structure C is derived from A, C :: A, But you can't write ab.c :: A, because the name from C :: A can know it does not belong to structure AB). The same ab.b :: abc (); will call B :: ABC. Note that both structures A, B, and AB have a member variable name C and type is long, then ab.c = 10; if it is ab. ABC (); same error? No, because there are three AB :: C, where there is a type and the type of AB match, the offset of the map is 28, so AB.C will return 3028. If it is desired to use the other two AB :: C mappings, the address of AB is shifted as described above by writing ab.a :: c and ab.b :: c. Note that due to the above statement, you can do this: void (ab :: * pabc) () = b :: abc; (ab. * PABC) () ;. The type of B :: ABC here is VOID (B ::) (), and the PABC does not match, but the b is the parent class of AB, so implicit type conversion will be performed. How to convert? Because B :: ABC maps the address, and the implicit type conversion To ensure that the THIS is turned B * before calling B:: ABC, it is to be added to B *. Since it is necessary to add this 12, but B :: ABC is not a mapping offset value, the PABC actually maps two numbers, one is the address corresponding to the B :: ABC, one is the offset value 12, the result PABC this pointer The length is no longer as 4 bytes as previously mentioned, and it becomes 8 bytes (more four bytes for recording offset values). You should also pay attention to the A_B, C, A :: C, etc., which should be written directly in AB:: ABCD, which actually add this->, namely A_B = B_B = 2; actually this-> a_b = THIS -> B_B = 2;, then, as above, this is shifted twice to obtain the correct address. Note that the implicit type conversion mentioned above is done because the permissions are met, otherwise it will fail. That is, if the above AB protects the inheritance A, the private inheritance B is private, and only the conversion can be converted in the member function of the AB, and the member of the subclass of AB will only use A member without using B, because of permissions restricted. The following will fail. Struct AB: Protected A, Private B {...}; struct c: public ab {void abcd ();}; void c :: abcd () {a_b = 10; b_b = 2; c = a :: c = B: : c = 24;} B_B = 2 here in C :: ABCD; and B :: C = 24; will report an error, because here is the subclass of AB, and AB private inherits from B, and its subclasses no right It looks like B.

But only if you don't make implicit type conversion, you can still be implemented by display type conversion. AB.A_A = 3 in the main function; ab.b_b = 4; ab.a :: abc (); all will report an error, because this is called in the outside world, no permissions, no implicit type conversion . Note that here C :: ABCD and AB:: Abcd are the same name, followed, the members variables of the subclass can be the same name with the parent class (above AB:: C and A :: C and B :: C) There is no problem with the member function. Usually, as mentioned above, the type matching is performed. Note that due to the function, the parameter change can be changed, which is the overload function. The virtual inheritance has already been said that when AB is generated, its length should actually be the length of the length of the length of AB plus the length of the AB's own defined member. That is, the example of AB is an instance of A. is an instance of B, is because an AB instance, which records only an instance of a A and records an instance of a B. There is such a case - vegetables and fruits are plants, marine organisms, and milk animals are animals. That is, the two parent classes inherited come from the same class. The assumption is as follows: struct a {long a;}; struct B: public a {long b;}; struct c: public a {long c;}; struct d: public b, public c {long d;}; void main ) {D D; DA = 10;} The instance of B above includes an example of a A, and the example of C also includes an example of a A. The example of D is included in an instance of B and a C, then D contains two examples of the instance. That is, when D is defined, inherit the mapping element of the two parent classes, generate two mapping elements, the names are d :: A, the type is long A ::, the mapping offset value is just 0. The result in the main function is D.A = 10; will report an error, which A cannot be confirmed. Is this not very strange? The names, types, and mappings of the two mappings are the same! Why don't you know them into one because they actually differ in the instance of D, one is 0 one is 8. Similarly, in order to eliminate the above problems, write D.B :: A = 1; D.c :: A = 2; to represent member a in different instances. But the type B:: A and C :: A is not all for long a ::? However, the name of the member variable or member function will also work in the type matching, so for DB :: A, because the type of the left is d, then look at the right side, its name is b, just good Is the parent class of D, first implicit type conversion, then look at the type, is A, the implicit type conversion again, then return to the number. Assuming the address corresponding to the above D is 3000, then D.C:: A first converts D this instance into an instance of C, thus offset the 3000 byte to return the number of address types of the LONG type 3008. Then convert the instance of A, offset 0, and finally returns 3008. One of the problems will be described above, that is, the member a that is inherited from A is only one example, not as two instances as above. Assuming that animals have a hungry member variable, it is clear that the whale should only be filled with a hunger. As a result, two hunger is very strange. In this regard, C proposes the concept of virtual success.

The format is to add keyword Virtual when inheriting the parent class, as follows: struct a {long a, aa, aaa; void abc ();}; struct B: Virtual public a {long b; }; What is the offset of B inheritance from a, b :: b? The length 12 of A will be no longer a, but 4. Inheriting the three map elements generated or the same as the original, just the name modification becomes b ::, the mapping is still constant. So why b :: b is 4? What is the previous 4 bytes? The above is equivalent to the following: struct b {long * p; long b; long A, aa, aaa; void abc ();}; long bdiff [] = {0, 8}; b :: b () {p = bdiff The top of the top of the top array BDIFF is directed to the top of the global array. What do you mean? The start of the beginning of B is used to record an address, which is equivalent to a pointer variable, which records the deviation value due to the virtual success in the memory identified by the address. The above BDIFF [1] means that the B instance is to be converted to a A instance, and the value of BDIFF [1] needs to be shifted, and BDIFF [0] means the offset value required to convert B instances to B instances. . Why do you have to turn B instance? Next description. But why is an array? Because a class can be done with multiple classes by multiple classes, each class requires the offset value to account for one element in the array of BDIFF, which is called the virtual class table. Therefore, when writing B; B.AAA = 20; long a = sizeof (b); when a value is 20, because there is a 4-byte to record the pointer described above. Assuming the address corresponding to 3000. First, the example of B is converted to a A example, which should be offset 12 and return 3012, but the compiler finds B is the virtual succession from A, then the offset value 8 should be obtained by B :: P [1], then return 3008, then add 816 to the B:: AAA map 8 and return 3016. Similarly, when B.B = 10;, since B:: B is not induced, 3000 plus the b :: b: 4 to 3004. For B.Abc (); will be converted to a an instance of B by B :: P [1] and then call A :: ABC. Why is it so troublesome like it? First let us know what is virtual (Virtual). The virtual is illustrative, is not true. For example, a vintage TV has 10 channels, that is, it can remember the frequency of 10 TV stations. Therefore, it can be said that the 1 channel is a central 1st, and the 5 channel is the central, the 7 channel is a Sichuan Taiwan. Here, the channel is a falsehood of the radio frequency, because the channel is not a radio frequency, just record the radio frequency. When we press 5 to change to the central 5 sets, it is possible that someone has tvged TV to make 5 channels no longer a central 5, but another TV station or a snowflake is not signal. Therefore, it is not guaranteed, it may be correct, because it must be indirect, in fact, is equivalent to the referenced reference. what is the benefit? I only remember 5 channels is the central 5th. When I don't want to see the central 5th, I changed to a central 2 set, but the same "5 channels" can get different results, but the program is not written, I only remember "according to 5 channels", it can be achieved to see 2 in the center. Therefore, virtual is indirect results, due to indirect, the results will be more flexible, which will be more flexible, which will show it when you explain the virtual function later. But the bad thing is more than a procedure (to be obtained indirect), the efficiency is lower.

Since the above virtual success, the inherited elements are virtual, that is, all the operations of the inherited map elements should indirectly obtain the offset value or address corresponding to the corresponding mapping element, but the inherited mapping element corresponds to the offset The value or address is constant, and the requirement for this red word is only implemented by implicit type conversion to change the value of this. Therefore, the offset value required by the B Turk A mentioned above is indirectly obtained by a pointer B:: P to manifest it. Therefore, starting the whale will have two hunger to make the oceanic organisms inheritance from animal virtual, so this member will indirectly use the hunger of the milk and marine life, then in the derived whale this When class, let the milk animal and marine creatures points to the same animal example (because all instances of animals are indirect, through the virtual inheritance to indirectly use the members of the animal), so when the whale is filled with hunger, no matter which hunger is filled Degree, actually populate the same. And C is just doing this. As follows: struct a {long a;}; struct b: Virtual public a {long b;}; struct c: virtual public a {long c;}; struct d: public b, virtual public c {long d;}; void Main () {d d; da = 10;} When inheriting from a class virtual, when the derived class is arranged (that is, it is determined in the offset value defined in the derived class definition "{}") First, arrange the pointer of the false item mentioned earlier to achieve indirect acquisition of offset value, then arrange each parent class, but if there is a false parent class inherited in the parent class, these parts will be removed first. Then arrange the mapping elements of the school. Finally, the class that has just been removed is inherited. At this time, if a class that is already inherited is discovered, it is not repeatedly arranged again, and no longer generates the corresponding mapping element. For the above B, the virtual inheritance A is discovered, first arranged in the previously said B: P, then arrange A, but found that A needs to be inherited by virtual, so excludes, arrange the mapping element of your own defined map B :: B, mapping The offset value is 4 (due to the occupation of B:: P). Finally, the mapping element B :: A generated inheritance is generated, so the length of B is 12. For the above D, it is found to inherit from C, so: Arrange D:: P, accounting for 4 bytes. Arrange the parent class B, found that A is inherited, rejected, so the inheritance map element B :: B (there is also the B: P: P) generated by the previous compiler, generate D:: B, 4 Bytes (compilers merge B:: P and D:: P :: p: P., later explain the virtual function). Arrange the parent class C, found that c needs to be inherited by virtual inheritance, reject. Arrange D :: D of his own defined, the offset value of its mapping is 4 4 = 8, accounting for 4 bytes. Arrange A and C, first arrange A, accounting for 4 bytes, generate D :: A. Arrange C, first align a in C, and find that it is virtual inheritance, and found that A, which is no longer c :: A to generate mapping elements. Next, C :: P and C :: C are arranged, 8 bytes, generate d :: c. Therefore, the length of the final structure D is 4 4 4 4 8 = 24 bytes, and only one D:: A, the type of long A ::, the offset value is 0.

If it is very fainted above, it is not tight, just give an algorithm to realize virtual inheritance, different compiler vendors give different implementation methods, so the results of the above may be incorrect to certain compilers. However, it should be remembered that all members of the class that are virtual inheritance must be indirectly obtained, as for how to indirectly, different compilers have different processing methods. Since the need to ensure indirect acquisition, for long d :: * pa = & d :: A; because it is long D: *, the compiler has false inheritance in the inheritance system of D, must guarantee indirect to its part It is obtained, so the PA will no longer be offset value, otherwise d. * PA = 10; will result in a direct access value (taken out of the content of the Pa), violating the meaning of virtual inheritance. In order to indirect access to the offset value recorded by the PA, it must be ensured that when the code is executed, it is indirect when the PA is d ::, while D :: D is not indirect. Obviously, this requires more and more complex code, most compilers have all used indirect access. Therefore, the length of the PA will be 8 bytes, one of which is offset, and a 4-byte record of one serial number. This serial number is used in the previously said false density to obtain the correct deviation due to virtual inheritance. Therefore, the value of the first element referred to in the previous B: P represents B instance transition to B instance, and is provided here to achieve all indirect acquisitions. Note that the above D:: P is different for different D, but their content is the same (both of which are the address of the virtual class table of structure D). When the example of D is just generated, the value of the D:: P of the instance will be a random number. In order to ensure that D:: P is correctly initialized, the above structure D does not generate a constructor, but the compiler will automatically generate a default constructor (no parameter constructor) to ensure D:: P and from C The correct initialization of the inheritance C :: P, the result will result in D D = {23, 4}; errors, because D has defined a constructor, even if it is not shown on the code. So what is the meaning of virtual success? It is functional as an example of indirect acquisition. From the type, there is no difference from ordinary inheritance, namely the virtual inheritance and the previous public, etc., just a syntax, there is no impact on the number of numbers. . See the meaning of the virtual function before you understand its meaning. The virtual function is induced by a function type mapping element. According to the virtual inheritance, it should be an address of this function, but the result is the value of the THIS parameter. In order to indirectly obtain the address of the function, C has put forward a syntax-virtual function.

When writing a function declaration or definition in type definition {}, add keyword Virtual before declaring or defining statements, as follows: Struct a {long a; virtual void abc (), bcd ();}; Void A :: abc () {a = 10;} Void A :: BCD () {a = 5;} The top is equivalent to the following: Struct a {void (a :: * pf) (); long a; void abc (), BCD (); A ();}; void a :: abc () {a = 10;} void A :: bcd () {a = 5;} void (A :: * AVF []) ( ) = {A :: abc, a :: bcd}; void A :: A () {pf = AVF;} The member A :: PF of A: A :: PF is the same as a pointer, pointing to an array This array is called virtual function table, which is an array of function pointers. When such a :: ABC is used, the serial number of A:: ABC is given in A::: PF, is indirectly obtained by A :: PF, therefore A; A.ABC (); will be equivalent to (a. * (a.pf [0]))) () ;. Therefore, the length of the structure A is 8 bytes, and then looks at the following code: Struct B: public a {long b; void abc ();}; struct c: public a {long c; virtual void abc ();}; structure BB: public b {long bb; void abc ();}; struct cc: public c {long cc; void abc ();}; void main () {bb bb; bb.abc (); cc cc; cc. CC = 10;} First, BB.ABC () is executed above, but the definition of BB :: ABC or B: :: ABC is given, so it will fail when it is compiled, but will fail. Second, no cc.abc () is executed; but it will say CC :: ABC is not defined in connection with the address of the address, why? Because of the example of the CC, CC :: PF needs to be initialized in the default constructor generated by the compiler to automatically generate, which requires the address of CC :: ABC to populate. Next, the following functions are given. Void b :: abc () {b = 13;} void c :: abc () {c = 13;} void bb :: ABC () {bb = 13; b = 10;} void cc :: abc () {cc = 13; c = 10;} For bb.bbc ();, although there are three BB :: ABC mapping elements, only one mapping element The type of VOID (bb ::) (), which maps the address of the BB :: ABC. Since BB :: ABC does not use Virtual to modify, it will be equivalent to BB.BB ::Abc (); instead of (bb. * (Pf [0])) ();, BB will be 13. For cc.ABC (); also, the CC will be 13.

For ((b *) & bb) -> ABC (); because the left side type is b *, it will be ((b *) & bb) -> b :: abc (); because B :: ABC is not The definition is defined, so it is equivalent to ((b *) & bb) -> b :: abc () ;, B will be 13. For ((c *) & cc) -> ABC ();, it will be ((c *) & cc) -> C :: ABC () ;,, C :: ABC is modified into virtual functions, then equivalent C * pc = & cc; (pc -> * (pc-> pf [0])) () ;. Here first converts the CC to C, offset 0. Then, according to PC-> PF [0], the address of the function is obtained, and CC :: ABC, C will be 10. Because CC is an instance of CC, CC.PF will be filled when it is constructed, then as follows: Void (cc :: * ccvf []) () = {CC :: ABC, CC :: BCD}; cc :: CC () {cc.pf = & ccvf;} therefore causes PC-> ABC (); the result call is called CC :: ABC instead of C :: ABC, which is due to the virtual reason for the indirect acquisition of function addresses. The same truth, for ((a *) & cc) -> ABC (); and ((a *) & bb) -> ABC (); all call CC :: ABC and BB :: ABC, respectively. However, please note, (pc-> * (PC-> PF [0]))) (); in the PC is a C * type, and the cc-> pf [0] returned CC :: ABC is Void (CC: :) () Type, and do the implicit type conversion how will the instance? If not, a member that will result in an error will result. As mentioned earlier, the length of each member of the CCVF is 8 bytes, and the two byte records need to be offset. However, most classes do not require offsets (if the CC instances above, it is deviated by the A instance), which is a waste of resources. The method given here is as follows, assuming that the address corresponding to the CC :: ABC is 6000 and assumes the address at the following label P is 6000, and the address corresponding to the CC :: A_thunk is 5990. Void cc :: A_thunk (void * this) {this = ((char *) THIS) DIFF; P: // CC :: ABC normal code} Therefore, the value of PC-> PF [0] is 5990, and Not CC :: ABC 6000. The above DIFF is the corresponding offset. For the above example, the DIFF should be 0, so the value of PC-> PF [0] is still 6000 (because the offset is 0, there is no need 5990). This method is called Thunk, indicating a short code for completing the simple function. For multiple inheritance, as follows: struct d: public a {long d;}; struct E: public B, public c, public d {long e; void ABC () {E = 10;}}; there will be three virtual functions Table, because B, C, and D bring a virtual function table (because from A derived).

The result is equal to: struct e {void (E:: * B_PF) (); long b_a, b; void (E:: * c_pf) (); long c_a, c; void (E:: * D_PF) () Long D_A, D; Long E; VoID ABC () {E = 10;} E (); Void E_C_THUNK_ABC () {THIS = (E *) ((char *) - 12); ABC (); } Void e_d_thunk_abc () {THIS = (E *) ((char *) THIS) - 24); ABC ();}}; void (E:: * E_BVF []) () = {E :: ABC, E :: BCD}; void (E :: * e_cvf []) () = {E :: E_C_THUNK_ABC, E :: BCD}; Void (E :: * E_DVF []) () = {E :: E_D_thunk_abc, E :: BCD}; E :: e () {b_pf = e_bvf; c_pf = e_cvf; d_pf = e_dvf;} Result E E; C * PC = & E; PC-> ABC (); D * Pd = & E; PD -> ABC () ;, assuming that the address of E is 3000, then the value of the PC is 3012, the value of the PD is 3024. Results The value of PC-> PF is E_CVF, and the value of PD-> PF is E_DVF, so that the offset problem is solved. Similarly, for the previous virtual success, when there are multiple virtual class tables, such as: struct a {}; struct B: Virtual public a {}; struct c: Virtual public a {}; struct d: Virtual public a {}; Struct e: public b, public c, public d {}; this is the E will have three virtual class tables, and each virtual class table will be correctly initialized in the default constructor of E to ensure virtual inheritance Meaning - indirect acquisition. The reason why the above virtual function table is then so complicated is just to ensure indirect acquisition. It should be noted that the type of E_BVF is defined as Void (E :: * []) ​​() is just because of the demo, I hope to write as many syntax on the code, do not mean that the type of virtual function can only be Void (E :: (). The actual virtual function table is only an array, and the size of each element is 4 bytes to record an address. Therefore, it can also be as follows: struct a {virtual void abc (); Virtual float abc (double);}; struct b: public a {void ABC (); float abc (double);};, b b; a * pa = & b; PA-> ABC (); B: ABC (34) of the type of VOID (B::); and PA-> ABC (34); will call the type of float (b ::) (double) B :: ABC. They belong to the overload function, even if the name is the same, it is two different virtual functions. It should also be noted that Virtual and previous publications are just some of the information provided to the compiler, and the information given is for certain special circumstances, rather than all applications where the number is used, so it cannot be used as The type of number.

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

New Post(0)