Doc No: X3J16/94-0183 WG21/N0570 Date: 27 September 1994 Project: Programming Language C++ Reply To: Richard Minner Island Graphics Corporation 5201 Dover Avenue Sacramento, CA 95819-3824 rtm@island.com Polymorphism During Construction and Destruction 1. Introduction 2. Definitions of Terms ("Glossary") 3. The Life and Times of an Object 4. Common Proposal 5. Bruns Option 6. Skaller Option 7. Glassborow Option Appendix: Other Options Discussed 1. Introduction ---------------------------------------------------------------------- This paper presents proposals covering the behavior of virtual functions, typeid() and dynamic_cast<> during non-trivial construction and destruction. The proposals are based on extensive c++std-core reflector discussion. Sections 2 and 3 define terms and concepts upon which the proposals are based. These sections are extensive but follow common and WP usage as much as possible: the proposals should be understandable without a detailed reading of these sections. Section 4 presents proposals for which there was consensus on the reflector, covering the bulk of the issues. Sections 5,6,7 present specific options for key aspects for which viable but mutually exclusive options were identified. The sections are numbered in the order these options appeared on the reflector: no bias is intended. The consensus was that there has been sufficient discussion and understanding to allow the committee to select an option advisedly. The hope is that these issues may consume little WG time and very little full committee time. [Note: I will not attend the Valley Forge meeting; it was agreed beforehand that it should not be necessary for me to present or champion these proposals myself.] 2/24 This paper is written in the context of three papers by Josee Lajoie related to the object model, in outline: Memory Model Object Shape Polymorphism (this paper) Const Objects Due to deadline constraints there will likely be some inconsistencies between this and the other three papers. The reflector discussion included c++std-core messages: 4268 4274 4278 4280 4281 4286 4288 4292 4294 4296 4299 4300 4301 4303 4305 4306 4308 4311 4315 4316 4320 4322 4325 4326 4327 4328 4329 4331 4332 4337 4346 4347 4348 4355 4359 4362 4369 4370 4371 4372 4376 4377 4378 4379 4380 4381 4386 4387 4389 4390 4391 4392 4393 4396 4397 4398 4399 4403 4406 4407 4408 4411 4412 4413 4415 4416 4417 4418 4419 4420 4421 4422 4423 4424 4425 4426 4428 4429 4430 4431 4432 4433 4436 4437 4439 4441 4442 4454 4457 4459 4460 4466 4468 4471 4473 4474 4475 4476 4477 4478 4480 4481 4482 4483 4485 4490 4500 4501 4503 4510 4511 4512 4515 4516 4518 4522 4531 4534 4537 4539 4544 4550 4565 4654 (whew) Posted by: Bill Gibbons Bill_Gibbons@taligent.com Francis Glassborow Francis@robinton.demon.co.uk John Bruns johnb@crt.com John Max Skaller maxtal@physics.su.OZ.AU Jonathan Shopiro shopiro@disney.etcore.ml.com Josee Lajoie josee@VNET.IBM.COM Mark A. Terribile mat@mole-end.matawan.nj.us Martin J. O'Riordan martino@theheart.ie Mike Ball Michael.Ball@Eng.Sun.COM Nathan Myers myersn@roguewave.com Neal Gafter gafter@clare.Eng.Sun.COM Richard Minner rtm@island.com Scott Turner pkt@lpi.liant.com Sean Corfield sean_corfield@prqa.co.uk Note: The reflector discussion was intermixed with discussion of sequence points in the mem-init list and details of shape acquisition, but these aspects of construction are not covered by this paper. (Messages specific to these aspects are not included in the list above.) 2/24 3/24 2. Definitions of Terms ("Glossary") ---------------------------------------------------------------------- While this section is perhaps overly long, many terms here are defined as in the 26 May 1994 WP -- to the best of my understanding -- and are included for reference and completeness only. Items are marked as follows: Follows WP. + Follows WP intent with added precision. * New term, or usage different from WP. A detailed reading here should not be necessary for understanding the proposals; this section can be taken primarily as a glossary. The ambiguities and inconsistencies of existing WP and common usage show themselves with some intensity when trying to define precisely the virtual aspects of objects under construction. The motivation for this section is to specify the proposed semantics with enough precision that they may be approved independently of the specific terms used. Clearly, there could be substantial work respecifying these semantics were the final WP terminology to be significantly different from that presented here, but I expect that is unlikely. As an essentially orthogonal and informal proposal, I ask that the committee give due consideration to the new and extended definitions presented here, at least as one source for further work on terminology. Some definitions here are not entirely complete or polished; []'s enclose areas of uncertainty or incompleteness, as well as comments. For clarity I have included some constraints that I believe to be implied by the WP and for which I believe there is no contention. The definitions are grouped as: 2.1 Types 2.2 Objects 2.3 Expressions There is significant cross-reference among the groups. Also, terms relating to object time intervals are defined in the next section, in particular object duration and lifetime. I should acknowledge here a comment by John Max Skaller (core-4501): ... terminology is that which is used to describe a model, and different models require different terminology. 3/24 4/24 The point being that choice of terminology unavoidably biases one's selection among semantic options. While I have made every effort to define "neutral" terms, I will admit here that I know these terms to be not entirely compatible with John Skaller's model of object behavior, hence this paper may contain a bias against his proposed option. I would also like to thank John for an extensive review and discussion of terminological options. 2.1 Types ---------- +type A compile time entity: fully _defines_ object size, shape and behavior (not address or state). [I am not sure that the WP requires "type" to imply _full_ specification of behavior.] incomplete type [As in WP] *partial type Type that incompletely specifies shape and/or behavior. [Introduced as more general than the specific WP usage of incomplete type.] class A full class (type) or base class (partial type), defined by a single class definition. *full class A type defined by a single class definition, i.e. not a partial type. An instance of a full class is a whole object, but not all whole objects are class objects, for example int objects. [Might prefer the term "whole class" to match whole object, but full is used to mean "not a part of another", and a full class as defined here is a class type that is not a part of another class type.] polymorphic class A class whose definition declares or inherits a virtual function. [WP 10.3] +polymorphic type A polymorphic class; there are no other kinds of polymorphic type. [WP 10.3 defines only polymorphic class, but the intent is clear.] 4/24 5/24 +base class of a class. A class used as a base of another class, directly or indirectly. A base class by itself is in general a partial type: it does not fully specify behavior because of virtual functions. A base class partial type is completed by specifying its location in a particular class DAG. Each such completion defines a distinct type. An instance of a base class is a base subobject, and vice versa. +direct base class of a class. The classes directly declared as bases of a class are its direct base classes. A virtual base class of a full class is a direct base class of that full class, even if not declared directly by the full class. indirect base class of a class. A base class that is not a direct base class. virtual base class of a class. A base class declared virtual. +abstract base class A class definition that includes one ore more (ABC) pure virtual functions defines an abstract base class. Such a definition does not define a class (type): it defines only base classes (partial types). An ABC may or may not provide definitions for its pure virtual functions. +class definition [As in WP.] Each class definition defines either a single full class (type) or an abstract base class. In addition, each class definition may define any number of base classes (partial types). class DAG The directed acyclic graph of classes representing a single full class and all its base classes. The direction of the graph arcs is upwards towards base classes. +class subDAG The upward subDAG from a specific class (node) in a class DAG: includes that class and all its bases. A class DAG _is_ a class subDAG of itself: subDAG here means "improper subDAG". 5/24 6/24 2.2 Objects ------------ instance of a type. Each object is an instance of its type at any given time. A single object may change type over its duration. +object An instance of a type: _has_ size, shape and behavior as defined by its type, plus state and an address. object size The number of bytes [bits for a bitfield object?] of storage occupied by the object. *object shape The layout in storage of an object's subobjects: taken with the object's address the shape allows the subobjects to be addressed (only). *object behavior [Difficult to define precisely. In essence the object behavior is the set of operations ("methods") that can be applied to the object. Each operation has a signature (input/output specification) and and a body (procedure).] actual behavior The actual behavior of a particular object in a particular program for a particular implementation. This term arises in the context of "undefined behavior", which means in essence that the actual behavior is not defined by the standard, and therefore could be anything; in actuality the behavior will be one particular thing: the actual behavior. The distinction is important where the standard says of two behaviors that they are undefined: we may then say that those behaviors are "the same" (as specified by the standard), even though the actual behaviors may differ widely. *object state The bit pattern in the object's storage. [This definition does not consider "usable" vs. "pad" bits.] +object address Unique identifier of the object's location in storage. Note that even zero-sized objects (certain base subobjects) have an address. subobject An object contained within another object; that is, one whose address is within the storage extent of another object. 6/24 7/24 An object is not a subobject of itself: subobject means "proper subobject". The kinds of subobject follow from the rules of type composition, summarized as: base virtual non-virtual member class array union Note: A base subobject _does_ include all of its base subobjects; that is, it is not a "differential subobject". *whole object An object that is not a base subobject. Base subobjects are singled out as "not whole" because only their behavior can depend on the types and states of their containing objects (via virtual functions). *the whole object of an object x. For each object x, the whole *(x's whole object) object of x is defined and found (tail recursively) as follows: if x is a whole object x else the whole object of any object that directly contains x The rules of object composition ensure that there will be exactly one whole object of each object x. *full object Object that is not a subobject. [WP uses "complete object" for this, but not entirely consistently; ARM defines "complete object" as whole object is defined here; common usage is also inconsistent. Lastly, "full object" is consistent with "full expression" where full means "not a part of another". A clear distinction between full and whole objects it important.] *the full object of an object x. For each object x, the full *(x's full object) object of x is defined and found (tail recursively) as follows: 7/24 8/24 if x is a full object x else the full object of any object that contains x The rules of object composition ensure that there will be exactly one full object of each object x. +class object An object that is an instance of a class during its lifetime. (During construction and destruction the object might not be such an instance in the proper sense.) +type of an object. Fully defines the object's shape +(object's type) and behavior. A given object's type can vary over its duration, but not its lifetime. [Unions are special; not covered here.] During construction and destruction the behavior of an object may be only partially defined, in which case the object has only a partial type and no type. The type of a base subobject during its lifetime includes its class DAG plus the subobject's class's location in that DAG: this is necessary to fully specify its behavior. This type can be specified minimally as the path from the whole object's class to the base subobject's class: the remainder of the DAG is implied and the path uniquely identifies the location. +class of an object. The class of a class object. +(object's class) Other objects have no class. :-) During the lifetime of a whole class object its class is its type (a full class). The class of a base subobject is a base class (partial type) that defines only the base subobject's: bases data members non-virtual member functions (completely) virtual member function _signatures_ (only) The runtime bindings for a particular base subobject are not known from its class alone. 8/24 9/24 most-derived class of an object. Applies only to class objects, for which it is the class of the object's whole object. +class DAG of an object. Applies only to class objects. +(object's class DAG) For each such object x, with whole object w, the class DAG of x is the class DAG of w's class (a full class). +base subobject DAG of a class object. The DAG consisting of the object's whole object and all the whole object's base subobjects; isomorphic to the object's class DAG. Note that each object in the DAG contains all the objects in its subDAG. +base subobject subDAG of a class object. The upward subDAG from the object in its base subobject DAG. Includes the object and all its base subobjects. A base subobject DAG _is_ a base subobject subDAG of itself: subDAG here means "improper subDAG". +subobject DAG of an object. The DAG consisting of the object's full object and all the full object's subobjects (bases and members, recursively). *virtual type of an object. Applies only to polymorphic class objects; other objects have no virtual type. Given an object b of class B, b's virtual type is that class D such that a virtual function B::f() _would_ dispatch to D::f(), _if_ every class in b's class DAG provided a definition of f(). Note that this does not mean that every virtual function of B _will_ dispatch to a D function definition. The virtual type of every object is one of: undefined none some class Two virtual types are the same if they are both undefined, even if the actual virtual behaviors differ. 9/24 10/24 +virtual behavior of an object. Applies only to polymorphic class objects; other objects have no virtual behavior. The complete set of virtual function bindings for a class object; finer grained than virtual type, which is always a single class. The distinction between virtual type and virtual behavior is seen in this example: struct A {}; struct B { virtual void f(); virtual void g(); }; struct D : A, B { void f(); }; D d; A& a = d; B& b = d; virtual virtual type behavior a none none b D { D::f(), B::g() } Comparison of an object's type, class and virtual type: struct B { virtual void f(); }; struct D : B { void f(); }; B b; D d; B& bd = d; b d bd object's type B D D::B class B D B virtual type B D D The notation 'D::B' here means: B base class of D. Some constraints on objects: + 1. A whole object (except a bitfield) consists of one or more contiguous bytes. [A bitfield consists of one or more contiguous bits?] Implication: a base subobject may have zero size or be discontiguous. + 2. No two whole objects overlap; this implies that no two whole objects have the same address. 10/24 11/24 + 3. Given two base subobjects b1 and b2, neither of which contains the other, then b1 overlaps b2 iff they have a common virtual base subobject with non-zero size and they have the same whole object. + 4. During the lifetime of a class object its virtual type is the type of its whole object. Corollary: during the lifetime of a whole class object its type, class and virtual type are the same. 2.3 Expressions ---------------- (Expressions are compile-time entities with runtime referents.) static type of an expression. [Essentially means compile-time type, i.e. not affected by the (runtime) referent of the expression. WP 1.3] *object type of an expression. Shorthand for "the object type of the referent of an expression", which implies "at a particular point in runtime". +class of an expression. Applies only to expressions with static class type. The class of such an expression _is_ its static type. This is seen to be a shorthand for "the class of the referent of an expression". +dynamic type of an expression. Applies only to expressions with polymorphic static type. For such expressions, the dynamic type is the virtual type of the referent. [WP 1.3] 3. The Life and Times of an Object ---------------------------------------------------------------------- This section defines terms for specific intervals in time (phases) with respect to an object. It reflects my understanding of the consensus and WP implications: as with the terms in the previous section this is not a formal proposal that these specific intervals and terms be adopted; rather these intervals and their ordering are the basis from which the primary proposals are made. The intervals occur always in the order shown, with recursive application for subobjects as indicated. The details of each interval are not given here; the diagram and ordering provide context for specifying those details elsewhere. In particular, 11/24 12/24 certain intervals may be vacuous for some objects: for example an object with no bases has a vacuous base construction. Two particularly important cases of vacuous intervals are: 1. Memory acquisition and memory release are vacuous for all subobjects. 2. 5c-6c and 6d-5d are vacuous for all whole objects. Adjustments to virtual type are not shown as intervals; rather the virtual type of objects will be specified with respect to the intervals. Time proceeds downward Intervals Points ----------------------------- 0c Memory Acquisition ===================================================== 1c | | Shape Acquisition [1] | | ----------------------------- 2c | Object Base Construction | Construction ----------------------------- 3c | | [4] Member Initialization [3] | | ----------------------------- 4c | | Constructor Execution [5] | | ----------------------------- 5c Object | Finish whole object Duration | construction | ============================================= 6c | Object lifetime | ============================================= 6d | | For this object's whole | | object: 5d-3d plus the part | | of 3d-2d up to this object | Object ----------------------------- 5d | Destruction Destructor Execution [5] | | [4] ----------------------------- 4d | | Member Destruction | | ----------------------------- 3d | | Base Destruction | | ----------------------------- 2d | | Shape Loss [2] ===================================================== 1d Memory Release ----------------------------- 0d 12/24 13/24 [1] Acquiring shape means at least the ability to address direct bases and members. Beyond that there are four essentially independent types of recursive shape acquisition: 1) bases of bases 2) members of bases 3) members of members 4) bases of members Which (if any) recursive application is chosen determines for which types of subobject shape acquisition is vacuous, but it does not affect the overall ordering of the intervals. [2] Shape loss is included for symmetry, but should be much less interesting than shape acquisition. There appears to be no reason to lose shape incrementally, i.e. it is probably simply vacuous for all but full objects. [3] Implies construction, i.e. recursive entry of the construction interval sequence. [4] An object's construction ends only when its whole object's construction ends; conversely an object's destruction begins as soon as its whole object's destruction begins. (Note: whole object, not _full_ object.) [5] The constructor/destructor execution intervals cover exactly the ctor/dtor function bodies: from opening to closing brace. They are parts (only) of construction and destruction and do not include member/base construction/destruction. [This distinction may be too fine. If we want "ctor execution == construction" we will need a different name for 3c-4c/4d-3d, for example constructor and destructor _body_ execution.] Each point in time is referenced with respect to the intervals it delimits, for example 1c may be called any of: End of memory acquisition Start of base construction Start of object construction Start of object duration 13/24 14/24 4. Common Proposal ---------------------------------------------------------------------- The proposals are ordered such that later proposals may in general assume the acceptance of earlier proposals, but not the reverse. In particular, proposals after 4.1 do not (much) discuss typeid() or dynamic_cast<>, referring instead just to virtual type (of objects) and dynamic type (of expressions). About the examples ------------------ The proposed rules are here expressed in terms of a completely general class DAG and should be considered in that context. Examples are presented only to aid comprehension. The following sample class hierarchy is used throughout: struct A { virtual void f() = 0; // pure virtual void g() { f(); } // non-virtual, calls virtual A(); ~A(); }; struct B : virtual A { int i; void f(); // overrides A::f() virtual void h(); // only in B and E B(); ~B(); }; struct C : virtual A { void f(); // overrides A::f() C(); C(B& b); // can receive B sibling ~C(); }; struct D { virtual void d(); // only in D D(); ~D(); }; struct E : B, C, D { void f(); // overrides A,B,C::f() void h(); // overrides B::h() E() : C(this) {} // pass B sibling to C::C() ~E(); }; The C::C(B& b) illustrates how it is in general possible for a base subobject constructor to gain access to a "sibling" base subobject; that is, an object outside its own subDAG. 14/24 15/24 The class DAG is: A / \ B C D \ | / \ | / E Construction order is A,B,C,D,E. Proposal 4.1 ------------ Virtual type, typeid() and dynamic_cast<> always "agree". They are either all defined or all undefined. If they are all defined, and given expression E with polymorphic static type and referent (object) x, then: a) typeid(E) returns the virtual type of x (== dynamic type of E). b) There is a unique object d in the base subobject DAG of x, such that d is or contains x, and such that dynamic_cast<> applied to E acts as if d were the whole object of x; the virtual type of x is the class of that d. 1-Pro: It was generally felt that for these three aspects of polymorphism to disagree would cause confusion and heartache. 2-Pro: Requiring that they _disagree_ in some instances could impose significant implementation costs, by not allowing them to share underlying mechanisms. 3-Con: Only that maintaining this condition could possibly overconstrain other aspects of polymorphism. No one seemed concerned about this possibility. Proposal 4.2 ------------ Virtual behavior during construction is exactly symmetric with virtual behavior during destruction. Basis, assumed already guaranteed: Given a full object f with subobject DAG G, there is a well-defined order of construction for all objects in G, and the order of destruction is the exact reverse. (Note that G includes all bases and members, recursively.) 15/24 16/24 Given any object x in G, then during constructor execution for x within construction of f, let cV represent all the virtual behaviors of all objects in G, and during destructor execution for x within destruction of f, let dV represent all the virtual behaviors of all objects in G; then cV is the same as dV. 1-Pro: Whatever we guarantee during construction we should also guarantee during destruction, and vice versa: users will assume this whether we provide it or not. Thus, for example, if it is difficult to guarantee something during destruction, we should not guarantee it during construction even if it would be otherwise simple to do so. 2-Con: None that I noticed. Proposal 4.3 ------------ Virtual type determines virtual behavior. Given the virtual type of an object x, plus the class DAG of x, plus the rules of virtual function overriding, the virtual behavior of x is completely determined. Implication: If other constraints lead to the actual virtual behavior of an object x being indeterminable in this way, then the virtual type of x must be undefined and so too its virtual behavior. In Scott Turner's original formulation (core-4407) he presented an example wherein some existing implementations gave this result: C::C(B& b) // B sibling { b.f(); // calls C::f(), overrides A::f() b.h(); // calls B::h(), there is no A::h() } This is the result of certain types of vtable sharing, and means that the virtual behavior of a the B subobject cannot be defined by a single class, i.e. it cannot have a virtual type. This proposal makes such actual virtual behavior unspecifiable and hence undefined. 1-Pro: It is too complex and confusing to specify rules for virtual behavior that are not bound to a single class (the virtual type of the object); nor would such rules be particularly useful. 2-Con: Not really discussed directly, except Scott noting that it was simpler to think of there being "only one f()". 16/24 17/24 3-Con: This proposal constrains our options for defining virtual behavior in general. 4-Pro: The constraining aspect of this proposal is felt only when one wishes to define virtual behavior "outside the current subDAG". The two options proposing that are Turner/Minner (withdrawn), and Skaller; Skaller has agreed with this proposal that an object should have a (single) virtual type, not a "hybrid" with possible dispatches to sibling classes. That is, b.f() above should call B::f() if b.h() does, and should _never_ call C::f(). Proposal 4.4 ------------ The virtual type of an object x is one of: undefined, its class, the class of an object of which x is a base subobject. This just says that virtual dispatch on an object should never be defined to go to a sibling. Note from the definition of virtual type that this of course allows actual dispatch to a base subobject of x, i.e. dispatch can go strictly up or down the graph, but not "sideways". 1-Pro: Assuming Proposal 4.3, there was general agreement that this is the only sensible behavior. 2-Con: None. Proposal 4.5 ------------ Prior to member initialization the virtual type of an object is undefined. During both of member initialization and constructor execution the virtual type is well defined and constant. 1-Pro: No one wanted to establish virtual type sooner, during base construction. To do so would be of little use and would complicate things for specification, and possibly for implementors. 2-Pro: Leaving virtual type undefined until after construction is too severe, and goes against existing practice and the ARM. 3-Pro: Having virtual type undefined during member initialization but defined during constructor execution seemed inconsistent to most; member initialization is considered part of the constructor: to have different behavior in member initialization would be an arbitrary inconsistency. For example, in: 17/24 18/24 B::B() : i(f()) {} vs. B::B() { i = f(); } it was felt that the calls to f() should behave the same, even noting that initialization and assignment are themselves not necessarily the same. 4-Con: Virtual (or other) member function calls on an object during its own member initialization is inherently dangerous: class invariants may not yet hold; we should not provide well-defined behavior for inherently dangerous practices. Thus we should leave virtual type undefined until after member initialization. 5-Pro: While potentially dangerous, such calls are not _necessarily_ dangerous. Class invariants are complex and beyond the scope of the standard; just because they might not hold does not mean we should leave a behavior undefined. Even if something is dangerous, it is better to define it if there is no significant cost to doing so: portability is enhanced by consistency of behavior, including consistency of incorrect behavior (reduces chance of latent defects). 5. Bruns Option ---------------------------------------------------------------------- Proposal 5 ---------- Each object constructor controls the virtual type of its subDAG: During member initialization and constructor execution for an object x, the virtual type of x and every base subobject of x is the class of x; the virtual type of any other object in x's base subobject DAG is undefined. In particular, this applies equally to virtual base subobjects. The result is that the virtual type of a virtual base subobject "jumps" among the derived classes that share it. Examples: The virtual type of the A subobject goes through the following sequence: Object Virtual constructor: type of A subobject: A A B B C C D undefined E E 18/24 19/24 // assume whole object of type E C::C(B& b) // sibling B passed from E { b.f(); // undefined b.h(); // undefined A* a = &b; assert(dynamic_cast(a) == 0); // note! f(); // calls C::f() ((A*)this)->f(); // calls C::f() g(); // A::g() --> C::f() } // assume whole object of type C C::C() { g(); // A::g() --> C::f() } Both calls to g() have the same virtual dispatch. 1-Pro: Having each object control its subgraph provides consistent virtual behavior in its constructor, regardless of whether the object is a whole object or base subobject. 2-Pro: Even though an object may not have initialized the virtual base subobject, that does not mean it should not be able to rely on consistent virtual dispatch via that base subobject: the language cannot guarantee exact consistency but the programmer can, by ensuring that the virtual base is initialized appropriately by the most-derived class. 3-Pro: Allows use of "pure services" in a virtual base, for example the function A::g(). This is a common idiom and should be allowed even with virtual bases. 4-Con: In the context of constructors, the idiom is not common, and perhaps is not even sensible. 5-Pro: That the virtual type of a virtual base subobject "jumps sideways" is appropriate considering the shared nature of virtual bases; by deriving virtually a class indicates a willingness to share and so should not expect total control during construction. 19/24 20/24 6-Pro: Providing defined behavior outside the current subDAG would provide little utility and possibly overconstrain implementations. In particular, with this proposal it remains unspecified whether there is a single virtual type per whole object throughout its duration, or an independent virtual type for each object in a base subobject DAG. 7-Pro: Of the three options, this is the most consistent with existing practice: the virtual type of virtual base subobjects follows the current constructor on each of several implementations tested and reported. 8-Con: Proposal 6: 1-Pro, 2-Pro, 3-Pro. Proposal 7: 1-Pro. 6. Skaller Option ---------------------------------------------------------------------- Note: Recently this option has been considered further and found to imply certain potential implementation costs not considered in depth in the reflector discussions. Skaller has agreed that the option "may not fly". However, the option is still viable in general and has specific conceptual advantages that should be considered. Proposal 6 ---------- Virtual type is bound to initialization; virtual type only progresses towards more derived: During member initialization and constructor execution for an object x, the virtual type of x and every non-virtual base subobject of x is the class of x; the virtual type of each virtual base subobject v is: the class of x if x is a whole object else the class of v; the virtual type of any other object in x's base subobject DAG is _unchanged_. Invariant implied: the virtual type of any object only ever progresses as follows: undefined its own class the class I of the object that initializes it a class derived from I That is, each object undergoes only successive refinement; its virtual type never "backs up" or moves "sideways", even for virtual base subobjects. Furthermore, establishing virtual type is connected to object initialization. Examples: 20/24 21/24 The virtual type of the A subobject goes through the following sequence: Object Virtual constructor: type of A subobject: A A B A C A D A E E // assume whole object of type E C::C(B& b) // sibling B passed from E { b.f(); // calls B::f() (unchanged) b.h(); // calls B::h() (unchanged) A* a = &b; assert(dynamic_cast(a) == 0); // note! f(); // calls C::f() ((A*)this)->f(); // calls A::f() -- error (pure) g(); // A::g() --> A::f() -- error } E::E() { ((A*)this)->f(); // calls E::f() } // assume whole object of type C C::C() { g(); // A::g() --> C::f() -- OK } 1-Pro: The invariant stated above is conceptually simple and valuable. 2-Pro: Only the object that initializes a virtual base subobject should set its virtual type. Since other objects in the DAG cannot rely on the specifics of the initialization they are better off accepting the definition provided by the virtual base itself, even if there is no such definition (pure virtual). 3-Pro: Potentially faster than Proposal 5 (Bruns) to establish virtual types when virtual base subobjects are present. 21/24 22/24 4-Con: While it is relatively simple to implement "no change to virtual type of other objects" during construction, mirroring that total virtual behavior during destruction is more difficult. For example, E::~E() on exit would have to adjust the virtual types of the A,B,C,D subobjects, and then B::~B() on _entry_ would still have to adjust the A subobject. In a more complex DAG the problem could be quite difficult and error prone, leading to very subtle compiler errors. In any case is not well supported by at least some existing implementations. 5-Pro: This implementation burden could be less than we think, and may still be worth the conceptual gain. 6-Con: There could be potential implementation space overheads during construction and destruction. 7-Con: Proposal 5: 1-Pro, 2-Pro, 3-Pro, 6-Pro, 7-Pro. Proposal 7: 1-Pro. 7. Glassborow Option ---------------------------------------------------------------------- Proposal 7 ---------- Each object constructor controls the virtual type of its subDAG, except that virtual base subobjects are not polymorphic at all during construction: During member initialization and constructor execution for an object x, the virtual type of x and every non-virtual base subobject of x is the class of x; the virtual type of each virtual base subobject v is the class of v; the virtual type of any other object in x's base subobject DAG is undefined. _After_ construction of a whole object w, the virtual type of all its virtual base subobjects is the class of w. Examples: The virtual type of the A subobject goes through the following sequence: Object Virtual constructor: type of A subobject: A A B A C A D A E A all done E 22/24 23/24 // assume whole object of type E C::C(B& b) // sibling B passed from E { b.f(); // undefined b.h(); // undefined A* a = &b; assert(dynamic_cast(a) == 0); // note! f(); // calls C::f() ((A*)this)->f(); // calls A::f() -- error (pure) g(); // A::g() --> A::f() -- error } E::E() { ((A*)this)->f(); // calls A::f() -- error } // assume whole object of type C C::C() { g(); // A::g() --> A::f() -- error! } 1-Pro: Provides the greatest degree of constructor consistency without regard to where in a class DAG a class resides. For example, a call to g() in a C constructor will _always_ fail. Since the C constructor can't be sure that it initialized the A subobject this is most appropriate. 2-Con: Proposal 5: 2-Pro, 7-Pro. Proposal 6: 1-Pro. 3-Con: Proposal 5: 3-Pro ("pure server" idiom) 4-Pro: Proposal 5: 4-Con (counter: not common in ctors) Appendix: Other Options Discussed ---------------------------------------------------------------------- (A) "Turner/Minner" This is a variant on Proposals 5 (Bruns), changing the virtual type of any other object in x's base subobject DAG is undefined. to the virtual type of any other object in x's base subobject DAG is _unchanged_. (As in Proposal 6 (Skaller).) 23/24 24/24 The intent was to provide more defined behavior at little implementation or specification cost. However, the argument in Proposal 6, 4-Con about difficultly of mirroring virtual type in the destructor convinced both Turner and Minner that the potential gain in defined behavior was minor in relation to the potential cost to implementors. Minner suggested on c++std-core withdrawing the option and no one objected. (B) "No polymorphism at all" The polymorphic type of every class object is its class (self) during construction and destruction: no polymorphism at all. This was suggested only half-seriously, following the Glassborow proposal, but did receive some favorable discussion as being the simplest and perhaps safest alternative. The bottom line was that it was too radical a departure from existing practice, with possible performance and implementation costs. 24/24