ISO/ IEC JTC1/SC22/WG21 N0804

 
Accredited Standards Committee X3       Doc No: X3J16/95-0204   WG21/N0804
Information Processing Systems          Date:   November 3, 1995
Operating under the procedures of       Project: Programming Language C++
American National Standards Institute   Ref Doc:
                                        Reply to: Josee Lajoie
                                                  (josee@vnet.ibm.com)
 
Object Model Issues and Proposed Resolutions
============================================
 
OB1- What are the requirements of the object model on object layouts?
 
  1.6[intro.object] paragraph 1:
    "An object is a region of storage and, unless it is a bit-field,
     or is part of another object and has a virtual base class,
     occupies one or more contiguous bytes of storage."
 
  I believe this is not quite right.
  12.7 [class.cdtor] paragraph 1 says:
    "For an object of non-POD class type, before the constructor begins
     execution and after the destructor finishes execution, referring
     to any nonstatic member or base class of the object results in
     undefined behavior."
 
  If I remember the object model discussions correctly, the rule in
  12.7 paragraph 1 was adopted because folks wanted to allow base
  classes and members of non-POD classes to be allocated on the heap.
 
  Proposed Resolution:
  1.6 paragraph 1 needs to be modified as follows:
    "An object is a region of storage and, unless it is a bit-field, or
     is an object of non-POD class type, occupies one or more
     contiguous bytes of storage."
 
569 - Is the mapping of members separated by access specifiers
      implementation-defined or unspecified?
 
  9.2[class.mem] paragraph 11 says:
    "The order of allocation of nonstatic data members separated by an
     access-specifier is implementation-defined."
 
  Shouldn't this be: unspecfied?
  Implementation-defined requires implementation to document how the
  members are mapped while unspecified doesn't.  (note: the same wording
  appears in 11.1 para 2).
 
  Proposed Resolution:
    9.2 and 11.1 should say that this mapping is unspecified.
 
533 - Is an anonymous union a type?
 
  9.5[class.union] paragraph 2 says:
    "A union of the form
       union { member-specification };
     is called an anonymous union; it defines an unnamed object (not a
     type)."
 
  Is an anonymous union a type?
  The sentence in paragraph 2 says it is an object with no type.
  Doesn't this break the object model?
 
  Is an anonymous union a class?
  Do the rules in the WP that apply to classes apply to anonymous
  unions as well?  The WP is unclear.
 
  Proposed Resolution:
    An anonymous union is neither a type or an object.
 
    Modify 9.5 paragraoh 2 as follows:
    "A union of the form
       union { member-specification };
     is called an anonymous union; it is neither an object nor a
     type."
 
529 - Can two base class subobjects be allocated at the same address?
 
    struct B { void f(); };
    struct L : B{};
    struct R : B{};
    struct D : L,R{};
 
  Since B has no data members, can B have the same address as another
  member subobject of of D?  That is, can a base class subobject have
  zero size?
 
  [note: Certain folks on the Core reflector have requested that the
  draft also be changed to allow complete objects and member subobjects
  to have 0-size.  I consider this to be an extension and will not cover
  it here.  I am only concerned with clarifying the requirements for the
  size of base class subobjects.]
 
  Discussion:
    During the discussions on the core reflector, three options were
    proposed:
 
    1) No, all objects in C++ (including base class subobjects) must be
       of non 0-size.
 
    2) Yes, base class subobjects can be of non 0-size.
       However, base class subobjects that are of non 0-size must still
       respect the restrictions in 5.9[expr.rel], paragraph 2 i.e.:
 
       "Pointers to objects or functions of the same type (after
        pointer conversions) can be compared; the result depends on the
        relative positions of the pointed-to objects or functions in the
        address space as follows:
        -- If two pointers to the same type point to the same object or
           function, or both point one past the end of the same array,
           or are both null, the compare equal.
        ...
       "
       What this means is that 0-sized subobjects are allowed, but that
       two subobjects of the same class type cannot be located at the
       same address location.
 
       If a complete object contains a single base class B, it may have
       zero size.  There is no way another B can have the same address
       since the complete object has a unique address.
 
       If a complete object contains several subobjects of type B, the
       compiler must ensure the subobjects have different offsets in the
       complete object.  This may require that some additional bytes be
       allocated between such objects.
 
       [Erwin Unruh in core-5430]:
           class B {};
           class L : B {};
           class R : B {};
           class D : L,R { int i,j; }
 
           Layout:
                   0       base class L
                   0       indirect base L::B
                   0       integer i
                   4       base class R
                   4       indirect base R::B
                   4       integer j
                   8       end of object (or start of next one)
 
       Not a single byte is wasted and both subobjects of type B have
       different addresses.
 
    3) Yes, base class subobjects can be of non 0-size and can be located
       at the same address location.  In this case, 5.9[expr.rel]
       paragraph 2 needs to be changed to indicate that the rule
       requiring that two pointers to the same type only compare equal
       if they point to the same object does not apply to base class
       subobjects.
 
  Proposed Resolution:
    Adopt solution 2)
    because I believe this is the status quo.
 
    9[class] paragraph 3 needs to be changed to say:
      "A class with an empty sequence of members and base class objects
       is an empty class.  Complete objects and member subobjects of an
       empty class type shall have a nonzero size."
 
    10[derived] needs to be updated to say:
      "A base class subobject can be of zero size.  However, two
       subobjects of the same class type cannot be located at the same
       storage location."
 
589 - Can the return type of an overridding function be incomplete and
      different from the return type of the base class function?
 
  10.3[class.virtual] paragraph 5 says:
    "A program is ill-formed if the return type of any overriding
     function differs from the return type of the overridden function
     unless the return type of the latter is pointer or reference
     (possibly cv-qualified) to a class B, and the return type of the
     former is pointer or reference (respectively) to a class D such
     that B is an unambiguous direct or indirect base class of D,
     accessible in the class of the overriding function, and the
     cv-qualification in the return type of the overriding function is
     less than or equal to the cv-qualification in the return type of
     the overridden function."
 
  If the return types are different and the return type of the
  overridding function is imcomplete, is the program ill-formed?
 
  Proposed Resolution:
    I believe that the rule above means that by the time the derived
    class overrides the base class virtual function, the return type of
    the virtual functions in both the base class and the derived class
    must complete class types.
 
  Add the following text after the text from paragraph 5 listed above:
    "The return type of the overriding virtual function shall be of a
     complete class type."
 
536 - When can objects be eliminated (optimized away)?
 
  Paragraph 15 indicates that an implementation is allowed to eliminate
  an object if it is created with the copy of another.
 
  ISSUE 1:
  --------
  However, this is in clear contradiction with other WP text:
  3.7.1[basic.stc.static] says:
    "If an object of static storage duration has initialization or a
     destructor with side effects; it shall not be eliminated even if
     it appears to be unused."
 
  3.7.2[basic.stc.automatic] says:
    "If a named automatic objects has initialization or a destructor
     with side effects; it shall not be destroyed before the end of its
     block, nor shall it be eliminated as an optimization even if
     appears to be unused."
  So which is right?
 
  Many have suggested different ways to resolve this difference:
 
  Andrew Koenig [core-5975]:
    The correct way to resolve the contradiction is to say that copy
    optimization applies only to local objects.
 
  Patrick Smith [core-6083]:
    1) Just weaken 3.7.1 and 3.7.2 so they can be overridden by the
       copy constructor optimization.
 
    2) Restrict the copy constructor optimization to only eliminate
       temporaries representing function return values.
 
    3) Require the programmer to explicitly mark the classes for
       which the copy constructor optimization is permitted even
       though it would violate 3.7.1 or 3.7.2.
 
    4) Require the programmer to explicitly mark the classes for
       which the copy constructor optimization is not permitted when
       it would violate 3.7.1 or 3.7.2.
 
  Solutions 3) and 4) require some sort of language extension to allow
  users to mark classes for which this optimization should be
  performed/ignored.  At this stage in the game, I believe most
  committee members are against the adoption of further language
  extensions.
 
  ISSUE 2:
  --------
  Jerry Schwarz in core-5993:
 
    What may be of concern is not side effects in general, but resource
    allocation.  E.g. if Thing is intended to obtain a lock that is held
    until it is destroyed, then you do indeed have to be careful about
    the semantics you give to the copy constructor.
 
      {
          Thing outer ; // get the lock
          {
              Thing inner = outer ; // copy constructor increments
                                    // count on lock.
 
              // do stuff that requires the lock
              inner.release() ;  // decrement count
              // do stuff that doesn't require the lock
          }
 
          // do stuff that still requires the lock.
      }
 
    The optimization allows outer and inner to be aliased, and the
    explicit release in inner may cause the lock to be released too
    early.
 
  Is Jerry's concern worth worrying about?
 
  Two possible resolutions were proposed:
 
  Jerry suggested the following:
      When we introduced the "explicit" keyword I remember considering
      what it would mean on copy constructors and thinking about the
      possibility that it would suppress this optimization.
 
  Jason Merrill proposed in c++std-core-5978:
      Perhaps the language in class.copy should be modified so that it
      only applies when the end of one object's lifetime coincide with
      the end of its copy's lifetime.
 
  Proposed Resolution:
    I believe the simplest solution at this point is to say that only
    temporary objects can be eliminated by this optimization.
 
    This resolution ensures that both Jerry's example and the rules
    in 3.7.1 and 3.7.2 still apply.