Doc No: |
<N 1877> |
Date: |
2014-09-29 |
Reply to: |
Max Abramson <[email protected]> |
|
|
Single Chain Plus Link Inheritance for C
Summary
This paper proposes the addition of single inheritance to structures used in the C programming language, plus the ability to derive also from any number of orphan abstract classes. Single inheritance has become the common case in modern object-oriented languages. The concept of interfaces is handled by allowing a special structure, written as an abstract orphan class with no instance variables to initialize. This should not be confused with @interface keyword used in Objective C to define a class.
Additionally, this static, single-inheritance approach to Object-Oriented Programming is simpler than that used in programs written in familiar dynamic languages such as C++, Java, Objective C, Python, C#, and others, while giving the programmer greater control over the means by which objects are initialized, if at all. This paper proposes features that may be added independently or together with the features proposed in “Adding classes to C” and/or “Access specifiers.”
class Ostrich : Animal {
private: //Note: only instance variables may be private or protected
int m_weight, m_height, m_strideLength;
//Note: C++ classes do not require forward declarations
public:
initOstrich(){/**/} //no-argument “{/**/}” proposed convention for //unfinished initializer
initOstrich(double speedGuess) { //overloaded initializer taking one double
double* guessPtr = malloc(sizeof(double));
(speedGuess) ? m_speedEst = speedGuess : m_speedEst = SPEED_LOOKUP;
++numRatites; //pre-increment is faster when working with objects
}
deleteOstrich() {
--numRatites; //static int numRatites, previously declared globally
}
deleteOstrich(double speedGuess) {
--numRatites; //static int numRatites, previously declared globally
free speedGuess;
}
};
For reference, C++ handles backward compatibility with C programs by implementing a structure using a class, though the default data members and methods remain public, by default, while C++ classes make members and methods private by default. C++ handles methods by building a list of pointers to those methods.
In C, a struct is able to use nested structures in order to implement the “has a” aggregate or composition design pattern, except that all data members are public, creating the problem of name collisions in large programs. Composition and aggregation have become preferred by most software developers to inheritance in object-oriented programming for the development of different design patterns, wherever composition can be used. For reference, aggregation might be thought of as “a community college has students,” while composition may be thought of as “a car is composed of many parts.”
For reference, C++ also allows a class to inherit from many parent structures. However, multiple inheritance can result in unreadable or unmaintainable code, as well as the “deadly diamond of death” problem for compilers where both parent classes share one or more grandparent classes. Java, Objective-C, C#, and most new object-oriented languages allow single inheritance plus the implementation of one interface. C++ and Python simply allow a programmer to implementing interfaces using multiple inheritance by adding an abstract class. Because all languages that use C as a subset need to remain compatible with a proposed, expanded version of ANSI C, this paper proposes only Single Inheritance. However, we briefly ponder the addition of one public, abstract orphan class that does not have any instance variables to initialize is intended for future implementations of the C language in order to allow for better support for modern Design Patterns and the interface feature found in Java and Objective C:
class Ostrich : public Ratite, public Runner{ //Runner is a bundle of methods
Example
For reference, C++ treats structures as classes for backward compatibility, but with the default access specifier changed to public. Again, the paper proposes strict conformance to the C++ standard (WG 21/N3337) in handling classes and structures for C. New C code must operate in exactly the same way as it would under C++, or the program must not compile, by default.
class Runner {
public:
const double SPEED_LOOKUP = 55;
double estRunningSpeed(double hipHeight, double strideLength){...}
double m_speedEst;
};
class Ratites : public Animal, public Bundle {
int m_numRatites; //private
public:
Ostrich bob; //this is the simplest way to nest structures
Runner runner; //no constructor/initializer is needed
void setNumRatites(int num) { //all methods are public
m_numRatites = num;
}
int getNumRatites() {
return m_numRatites;
}
};
Visually:
BaseClass
|
Animal AbstractBundle
\ /
Ratites
Ostrich bob
Runner runner
Here, the class Ratites derives instance variables, static members, constants, and methods from the structure Animal. It is also composed of Ostrich bob and Runner runner. However, in order to implement certain design patterns using the familiar single inheritance, we also allow Ratites to inherit methods, static members, and constants—but not instance variables—from the class AbstractBundle. The key here is that an initRatite() method needs only call the initBaseClass() method from BaseClass if there are instance variables to allocate memory for and initialize. Unlike C++ constructors and destructors, this proposal merely permits the programmer to allocate and/or initialize values explicitly. This may provide a good improvement in performance where memory allocation and initialization can be delayed at run time until it is absolutely known that these tasks must be carried out.
Single Chain Plus Link
This approach to inheritance is necessary in order to retain backward and forward compatibility with C++ compilers (and, to a lesser extent, Objective C and Java), while permitting the implementation of many interfaces already written for single inheritance languages. Unlike interfaces, which are generally bundles of virtual methods which must be overridden within the derived class, Single Chain Plus Link permits all of the methods to be both declared and defined in the abstract orphan class, which must contain no instance variables. In other words, an abstract orphan class or “Link” must not derive from other classes, contain any nested classes nor structures, nor contain any non-static variables. It may contain any number of non-virtual methods, constants, or static values that would not be initialized by a constructor/initializer.
Implementing interfaces this way is necessary because this paper does not propose virtual classes nor virtual methods, which must be overridden. For C programs that employ a C++ compiler, this approach requires no changes to compiler, other than the emission of warnings when the limits of the C specification have been exceeded. This author hopes that these proposed limitations on inheritance and nesting of classes on these abstract classes will be challenged and proven unnecessary.
Wording
The proposed wording draws heavily on the wording in WG21/N3337. Note: It is the intent of this proposal that access specifiers and other features conform to the specifications given in therein. Wording additions are included as four new keywords imported from C++, with identical meaning and syntax, and a new section building a strict, conservative subset of the access specifiers defined in N3337.
Add a new section 6.7.13, 6.7.14, and 6.7.15:
6.7.13 Nested structures
1 A nested structure is a member and as such has the same access rights as any other member. The members of an enclosing structure have no special access to members of a nested structure; the usual access rules for private, protected, and public shall be obeyed.
typedef struct Eagle {
int x; //public, by default
struct Bird { };
struct Golden {
void f(Eagle* ptrE, int i) { //methods must be public
ptrE->x = i; //OK: Eagle::Golden can access Eagle::x
}
}Eagle;
6.7.14 Derived classes
A list of base classes can be specified in a class definition using the notation:
base-clause:
: base-specifier-list
base-specifier-list:
base-specifier ...opt
base-specifier-list , base-specifier ...opt
base-specifier:
attribute-specifier-seqopt base-type-specifier
attribute-specifier-seqoptvirtual access-specifieropt base-type-specifier
attribute-specifier-seqopt access-specifier virtualopt base-type-specifier
class-or-decltype:
nested-name-specifieropt class-name
decltype-specifier
base-type-specifier:
class-or-decltype
access-specifier:
private
protected
public
The optional attribute-specifier-seq appertains to the base-specifier.
2 The type denoted by a base-type-specifier shall be a class type that is not an incompletely defined class
(Clause 9); this class is called a direct base class for the class being defined. During the lookup for a base
class name, non-type names are ignored. If the name found is not a class-name, the program is
ill-formed. A class B is a base class of a class D if it is a direct base class of D or a direct base class of one of
D’s base classes. A class is an indirect base class of another if it is a base class but not a direct base class.
A class is said to be (directly or indirectly) derived from its (direct or indirect) base classes. [ Note: See
6.7.11 for the meaning of access-specifier. — end note ] Unless redeclared in the derived class, members
of a base class are also considered to be members of the derived class. The base class members are said to
be inherited by the derived class. Inherited members can be referred to in expressions in the same manner
as other members of the derived class, unless their names are hidden or ambiguous. [ Note: The scope
resolution operator :: can be used to refer to a direct or indirect base member explicitly. This allows
access to a name that has been redeclared in the derived class. A derived class can itself serve as a base class
subject to access control, provided that a Link or orphan abstract class does not derive from another class. A pointer to a derived class can be implicitly converted to a pointer to an accessible unambiguous base class. An lvalue of a derived class type can be bound to a reference to an accessible unambiguous base class. — end note ]
3 The base-specifier-list specifies the type of the base class subobjects contained in an object of the derived
class type. [ Example:
typedef struct Base {
int a, b, c; //struct members are public by default
} Base;
typedef struct Derived : Base {
int b;
} Derived;
struct Derived2 : Derived {
int c;
} Derived2;
Here, an object of class Derived2 will have a subobject of class Derived which in turn will have a subobject of class Base. — end example ]
4 A base-specifier followed by an ellipsis is a pack expansion.
5 The order in which the base class subobjects are allocated in the most derived object is unspecified. [ Note: a derived class and its base class subobjects can be represented by a directed acyclic graph (DAG) where an arrow means “directly derived from.” A DAG of subobjects is often referred to as a “subobject lattice.”
Base
^
Derived1
^
Derived2
6 The arrows need not have a physical representation in memory. — end note ]
7 [ Note: Initialization of objects representing base classes can be specified in explicitly written initializers. — end
note ]
8 [ Note: A base class subobject might have a layout different from the layout of a most derived object
of the same type. A base class subobject may be of zero size; however, two subobjects that have the same class type and that belong to the same most derived object must not be allocated at the same address. — end note ]
6.7.15 Deriving from additional abstract base classes
A class or structure can be derived from any number of abstract orphan base classes. [ Note: The use of more than one direct base class can be called “Single Chain Plus Link Inheritance.” Interfaces and certain Design Pattern require class structure that derives from more than one class. The need to retain compatibility with C++ compilers — end note ] [Example:
class A { /∗ ... ∗/ };
class AbstractB { /∗ ... ∗/ };
class AbstractC { /∗ ... ∗/ };
class D : public A, public AbstractB, public AbstractC { /∗ ... ∗/ };
— end example ]
2 [ Note: The order of derivation is not significant except as specified by the semantics of initialization, cleanup, and storage layout. — end note ]
3 A class shall not be specified as a direct base class of a derived class more than once. [ Note: A class can
be an indirect base class more than once and can be a direct and an indirect base class. There are limited
things that can be done with such a class. The non-static data members and member functions of the direct
base class cannot be referred to in the scope of the derived class. However, the static members, enumerations
and types can be unambiguously referred to. — end note ] [Example:
class X { /∗ ... ∗/ };
class Y : public X, public X { /∗ ... ∗/ }; //error, must not compile
class L { public: int next; /∗ ... ∗/ };
class A : public L { /∗ ... ∗/ };
class B : public L { /∗ ... ∗/ };
class C : public A, public B { void f(); /∗ ... ∗/ }; //correct
class D : public A, public L { void f(); /∗ ... ∗/ }; //correct
— end example ]
4 A base class specifier refers to a non-virtual base class. For each distinct occurrence of a non-virtual base class in the class lattice of the most derived class, the most derived object shall contain a corresponding distinct base class subobject of that type.
5 A base class that is used as an abstract orphan class shall not contain nested classes, nor shall it derive from any other class. It may contain any number of static variables or constants, but must not contain instance variables nor virtual methods.
6 Polymorphic behavior by classes or methods is not part of this specification and shall not be allowed.