Doc No: SC22/WG21/N1445=03-0027

Project: JTC1.22.32

Date: Tuesday, January 21, 2003; revised February 25, 2003

Author: Francis Glassborow 

email: francis@robinton.demon.co.uk

Class defaults

1 The Problems

This is a set of proposals aimed at making several aspects of class definitions easier to teach and use, and more explicit. They also reduce the amount of (often redundant) code which must be written by the programmer, and therefore reduce the chance of introducing errors.

Currently we have no mechanism for forwarding from one constructor to another in the same class -- all initialisation code must be repeated in each constructor (or factored into a subsidiary function). Similarly, a derived class has no mechanism for reusing base class constructors other than by explicitly defining analogous constructors and forwarding their arguments to the base class in an initialiser list.

In addition the current compiler generated default destructor for classes with virtual members is non-virtual. It is generally advised that any class intended to be used as a base for inheritance -- a reasonable assumption if it contains a virtual function -- should have a virtual destructor to prevent memory leaks and other errors that might result from incomplete destruction of classes derived from it. So why shouldn't the compiler default to "doing the right thing"?

Itemised list:

    1. The declaration of a copy constructor in order to inhibit copy semantics also inhibits automatic generation of a default constructor. If one is required, it must be explicitly defined.
    2. Changing the compiler generated destructor to virtual for classes with virtual members affects binary compatibility of existing code. If such classes require a non-virtual destructor (especially for backward compatibility in code for COM and CORBA) then an easy way to overrule the new default is necessary.
    3. A class derived from a base with several constructors has to re-declare and define new constructors, which adds to maintenance where the base class constructors are essentially satisfactory.
    4. There is no way currently to forward from one constructor of a class to another. Sometimes the intent can be achieved via default parameters but this can force an unnatural parameter ordering.

2. Discussion

2.1 Compiler generation of special member functions.

            class base {        

            public:

                        base(int i, double d);

                        base(int i);

                        base(double d);

                        virtual foo();

            private:

                        double d_;

                        int i_;

            };

Purely in the context of the above definition the compiler will generate perfectly useable definitions of the copy ctor, copy assignment operator and [non-virtual] dtor. However the programmer will have to write explicit definitions for each of the three provided ctors. Using default parameters could absorb the third ctor into the first but still does not provide a mechanism for the second. That is an inconsistency because it makes the order of declaration of parameters significant. In addition the newcomer from Java will almost certainly try to use the Java technique for forwarding between ctors. That will compile but silently do something quite different from the programmer's expectations.

In addition we do not have a way of making explicit that the compiler will generate three functions (copy ctor, copy assignment and dtor). Good coding practice is to make all behaviour explicit and 'by code' is to be preferred to 'by comment'.

Good implementation tools might warn the programmer that s/he needs a virtual dtor. Good, but newcomers will just be confused by such a statement. Having got them to realise they need to write:

            virtual ~base();

They will be further irritated by the requirement to provide a definition. Now we find ourselves either placing a trivial definition out of line or entering into debate as to the desirability of in class definitions.

The proposals that follow remove all those problems and allow all programmers to explicitly state their intention in code. We will be able to write:

            class base {

            public:

                        base(int i, double d);

                        base(int i);

                        base(double d);

                        virtual foo();

                        default ~base();

                        default base(base const &);

                        default operator= (base const &);

            private:

                        double d_;

                        int i_;

            };

And define two of the ctors in terms of the first:

            base::base(int i): base(i, 0.0){};

            base::base(double d): base(0, d){};

 Being able to declare that the default compiler-generated dtor is explicitly wanted opens the way to changing the rule on what the compiler will generate. If the class definition includes any virtual functions, the compiler-generated default dtor will be virtual. For cases where the programmer explicitly wants a non-virtual compiler-generated dtor, the syntax above expresses that. Access specifiers can be applied to default declarators in the usual way.

In addition we can consider adding syntax to allow an explicit statement that the compiler is NOT to generate a default copy ctor or copy assignment operator. That will plug the hole where the current idiom fails (the compiler allows a call to a copy ctor in a class scope definition, but then optimises it away so the linker never gets to detect the lack of a definition) That would support a class definition such as:

            class objectbase {

            public:

                        objectbase(int i, double d);

                        explicit objectbase(int i);

                        explicit objectbase(double d);

                        virtual foo();

                        default virtual ~objectbase();

                        not default objectbase(objectbase const &);

                        not default operator= (objectbase const &);

            private:

                        double d_;

                        int i_;

            };

Note that we no longer have to worry about placing declarations in the private interface – which is conceptually wrong because they define the public behaviour of the class. The above syntax should make class design easier to teach, as well, since the novice no longer would have to learn the non-intuitive rule that one prevents copying and assignment by declaring the existence of functions for copying and assignment (and then not defining them). The statement of intent becomes part of the code instead of a comment. Errors of unintentionally calling the supposed-to-be-suppressed functions could be diagnosed by the compiler instead of waiting until link time (and receiving a hard-to-interpret error).

2.2 Inheriting base class constructors

Consider:

            class derived : public base {

            };

Currently the programmer has to duplicate all the base class ctors even if there is no extra data and inheritance is merely being used to provide an overrider for the virtual function or to provide extra functionality. Worse is that it provides a maintenance problem because the writer of derived has to track the ctors provided by the base even though the implementation of the base is of no interest to him/her.

Very curiously the class has an implicit copy ctor, copy assignment operator and a non-virtual dtor.

Derived classes that only add functionality are quite common in the field (though perhaps not so common in the code of expert C++ programmers). I believe we need to provide syntax to support automatic forwarding from derived class ctors to base class ones. The apparently obvious extension of using unfortunately has been compromised by the existing rules about access (a public using declaration makes protected base class functions public in the derived class). Protected functions inherited from a base class are implicitly protected in the derived class; the same principle should apply to constructors.

Therefore my proposal is for a syntax that will allow the programmer to write:

            class derived : public base {

            public:

                        default derived = base;

            };

If the derived class ctor needs to initialise members of the derived class, or to perform some other processing in its ctor, it can declare a ctor in the usual way and pass appropriate arguments to the base class in an initialiser list before entering the body of the derived ctor. If the declaration of the derived ctor exactly matches one inherited from the base class, the declared function supersedes the inherited one.

3 The Proposals

    1. The default keyword be used to support explicit declaration of a member function as one whose definition is to be compiler generated. In the first instance this should apply to the four members that can currently be compiler generated. Others might see this as an opportunity to extend explicit compiler generation to other common functions but that is not a direct part of this proposal.

A member declared as default will be compiler generated even if implicit generation would have been suppressed by the existence of another potentially overloading declaration:

class example {

               example(example const &);

public:

               default example(); //compiler will generate default ctor

               default operator =; //compiler will generate default assignment

               default ~example(); // compiler will generate destructor

};

Code generation takes place at the point of explicit declaration. If the function is never used in the program, it can be optimised out under the as-if rule. If compiler generation is not possible (because of a reference or const data member, for example), a diagnostic results (currently you only get a diagnostic on the implicit function if you actually use it.) The code generated by the compiler is the same as at present.

Otherwise the rules for when and how the explicit default functions are generated should be exactly as they are now for the implicit generation (only at need and inline).

    1. A class without a base that contains at least one virtual member shall generate a virtual destructor unless a destructor be explicitly declared. Optionally this destructor may be declared as default with or without virtual qualification and will then be generated as specified.

Note that this proposal does introduce silent change and needs to be closely examined. However I (as the proposer) believe that we should not just ignore the issue even if we eventually come down on the side of the status quo. Quite a number of experienced C++ programmers have been bitten by the problem that a polymorphic base needs an explicitly declared virtual dtor.

A default pure virtual destructor shall be both generated and require overriding in any derived concrete class. Thus, default ~example() = 0;

would not require the programmer to define an empty destructor body out of line simply to enable objects to be deleted through a pointer to their base class. A new syntax of the form:

default derived = base;

be introduced that imports all the base class constructors to the derived class. The access in the derived class is the same as that of the access in the base class. Such a declaration shall be ill formed if the base class has a private constructor, or if the derived class has more than one direct base, or if the inheritance graph contains any virtual bases.

Where this declaration is made, additional constructors may also be provided. Where such an additional constructor matches an imported constructor the additional one shall supersede the imported one.

This feature should be useful in generic programming, as a template could use its template parameter as a base class and automatically inherit all of its constructors.

    1. A new syntax shall be provided for constructor definitions wherein the constructor initialiser list is permitted to consist of a single constructor call to a constructor of the same type. Where the forwarding ctor has a non-empty body the body of the called ctor shall be first executed followed by the body of the forwarding ctor.

For example:

class mytype {

            int i;

            double d;

            std::string s;

public:

            mytype(int ival, double dval): i(ival), d(dval) {s = "ctor 1";}

            explicit mytype(int ival): mytype(ival, 0.0){ s = "ctor 2";}

            explicit mytype(double dval): mytype(0, dval){ s = "ctor 3";}

            mytype(): mytype(0, 0.0){ s = "ctor 4";}

            default mytype(mytype const &);

            default operator =;

            default ~mytype();

};

Therefore,

            mytype m;

results in the following sequence of events:

            i(0);

            d(0.0);

            s(""); // default string ctor

            // enter forwarded body

            s = "ctor1";

            // enter forwarding body

            s = "ctor4";

 

Changes to the Working Paper

These are not provided at this point. It seems more important to agree to the general principles.