2.19. Classes

Objects are instantiations of a Qore class. Classes define private members and methods, which are functions that operate only on the objects of that class.

Classes are declared with the following syntax:

class [namespace_path::...]class_identifier [inherits [private] [namespace_path::...]class_identifier[, ...]] {
    [private  $.var1[, ...];]

    [static] [synchronized] [private] [namespace_path::]method_name_identifier([$var1, $var2, ...]) {
         statements;
    }
    ...
}

Alternatively objects can be defined out of line as follows:

class [namespace_path::]class_identifier [inherits [private] [namespace_path::...]class_identifier[, ...]];

[static] [synchronized] [private] [namespace_path::]class_identifier::method_identifier([$var1, $var2, ...]) {
    statements;
}

Note

Private members can only be declared in an in-line class definition (the first example above).

Note

In a class hierarchy, base class constructor methods can be explicitly specified using a special syntax unique to subclass constructor methods. Please see Class Inheritance for more information.

Note

It's possible to write purely object-oriented scripts/programs in Qore by defining an application class and using the -x or --exec-class command-line arguments to tell Qore to instantiate the class instead of doing normal top-level execution (in fact, the --exec-class arguments disallow the use of top-level statements entirely). For more information, please see Command-Line Parsing and Parse Directives.

2.19.1. Private and Synchronized Methods

Methods declared with the private keyword can only be called by other member functions of the same class. Any attempt to call these methods from outside the class will result in a run-time exception.

Methods declared with the synchronized keyword will only run in one thread at a time.

2.19.2. Static Methods

Methods declared with the static keyword are like regular subroutines that are attached to the class. These methods are not associated with a particular object's state and therefore are not allowed to refer to object members or call non-static methods. Also, no reference to the special $self variable is allowed within static methods.

Static method calls take a special syntax as documented here.

2.19.3. Constructors, Destructors, and Other Special Methods

All class methods are optional, but some methods have a special meaning.

Table 2.88. Special Methods

Name

Description

constructor()

Called when objects are created with the new operator. User code may not explicitly call constructor() methods. In a class tree, constructor() methods are called for base classes first in left-to-right, depth-first declaration order.

copy()

When a user explicitly calls a copy method, Qore will generate a new object with references to the same members as the source object. Then, if there are any base classes, base class copy() methods are called in the same order as the constructor() methods. If a copy() method is defined, it will be run in the new object with a reference to the old object passed as the first argument. Any other arguments passed to the copy() method are ignored.

destructor()

Called when objects go out of scope or are explicitly deleted. User code may not explicitly call destructor() methods. In a class tree, destructor() methods are called for base classes in the opposite order in which the constructors are called

memberGate()

If this method is implemented in the class, it is called when read access is attempted to private member or members that do not exist in the current object. In a class tree, memberGate() methods are not inherited.

methodGate()

If this method is implemented in the class, it is called when methods are called on the object that do not exist in the current object. In a class tree, methodGate() methods are not inherited.

memberNotification()

If this method is implemented in the class, it is called when an object member is updated outside the class with the member name as the argument. In a class tree, memberNotification() methods are not inherited.


2.19.4. Object Members

When defining a class, members of the current object can be referred to with a special syntax as follows:

$.member_name_identifier

Furthermore, the automatic variable $self is instantiated which represents the current object (similar to the this in C++ or Java). Therefore if you need to access hash members which are not valid Qore identifiers, then enclose the member name in double quotes after the dot operator as follows:

$self."&member-name"

The automatic $argv local variable is instantiated as usual in all class methods where there are more arguments than variables declared in the method declaration.

If the class implements a memberGate() method, then whenever a non-existant member of the class is accessed (read), this method will be called with the name of the member as the sole argument, so that the class can create the member (or react in some other way) on demand. This method is also called when methods of the same class try to access (read) non-existant methods, but is not called from within the memberGate() method itself.

To monitor writes to the object, the class can implement a memberNotification() method, which is called whenever an object member is modified from outside class member code. In this case, the memberNotification() method is called with the name of the member that was updated so that an object can automatically react to changes to its members (writes to members) from outside the class. This method is not called when members are updated from within class member code.

2.19.5. Object Method Calls

Within a class method definition, calls to methods in the same class hierarchy (of the current class or a base class) can be defined as follows:

[namespace_path::]$.method_name([arg, ...])

For example:

# to call base class Mutex::lock()
Thread::Mutex::$.lock();
# to call lock() in the current (or lower base) class
$.lock();

This syntax can only be used to call methods in the current class or in base classes. This is because these calls are resolved at parse time, and only these classes are known and accessible at parse time. To call a derived class method from a base class, you must use the $self variable to call the method, so that the call will be resolved at run-time, for example:

# this way, "member" can be resolved to a derived class method
$self.member();

Calls to object methods can be made outside the class by using the above syntax as well. All such calls are resolved at run-time, therefore if the call is made to a private function outside the defining class, then a run-time METHOD-IS-PRIVATE (if the method is private) or BASE-CLASS-IS-PRIVATE (if the method resolves to a privately-inherited base class) exception will be raised.

2.19.6. Class Inheritance

Class inheritance is a powerful concept for easily extending and resuing object-oriented code, but is also subject to some limitations. This section will explain how class inheritance works in Qore.

Classes inherit the methods of a parent class by using the inherits as specified above. Multiple inheritance is supported; a single Qore class can inherit one or more classes. When a class is inherited by another class, it is called a base class. Private inheritance is speficied by including the keyword private before the inherited class name. When a class is privately inherited, it means that the inherited class' public members are treated as private members in the context of accesses outside the class.

It is not legal to directly inherit the same class more than once; that is; it is not legal to list the same class more than once after the inherits keyword. However, it is possible that a base class could appear more than once in the inheritance tree if that class is inherited separately by two or more classes in the tree. In this case, the base class will actually only be inherited once in the subclass, even though it appears in the inheritance tree more than once. This must be taken into consideration when designing class hierarchies, particularly if base class constructor parameters for that class are explicitly provided in a different way by the inheriting classes.

Note

Class members only exist once for each object; therefore if classes in an inheritance tree have different uses for members with the same name, then a class hierarchy built of such classes will probably not function properly.

Subclasses can give explicit arguments to their base class constructors using a special syntax (only available to subclass constructors) similar to the C++ syntax for the same purpose as follows:

class_name::constructor([$var1[, ...]) : base_class_identifier(expression(s))[, ...] {
    statements;
}

Here is a concrete example of giving arguments to an inherited base class:

class XmlRpcClient inherits Qore::HTTPClient {
    # calls the base class HTTPClient constructor, overrides the "protocols" key to "xmlrpc"
    constructor($opts) : Qore::HTTPClient($opts + ( "protocols" : "xmlrpc" ))
    ...
}

Because base class constructors are executed before subclass constructors, the only local variables in the constructor that can be referenced are those declared in the subclass constructor declaration (if any). What this means is that if you declare local variables in the expressions giving base class arguments, these local variables are not accessible from the constructor body.

Note

Base classes that give explicit arguments to their base class constructors can be overridden by subclasses by simply listing the base class in the base class constructor list and providing new arguments.

2.19.7. Object References

Like Java, in Qore, objects are treated differently from all other data types in that they are by default passed as arguments to functions and methods by passing a copy of a reference to the object. That means that passing an object to a function that modifies the object will by default modify the original object and not a copy, however reassigning a local parameter variable assigned an object passed as an argument (that is only assigned to a local variable in the calling function) will not result in deleting the object, but rather decrement its scope reference count (note that if the object were created as a part of the call and reassigning the variable would cause the object's scope reference count to reach zero, then the object would be deleted in this case).

Assigning an object to a variable has the same effect; a copy of a reference to the object is assigned to the variable. This results in prolonging the object's scope (by owning a new copy of a reference to the object).

An example:

sub test2($x) {
   # we can modify the original object like so:
   $x.member = "tree";

   # here we re-assign $x, but since the object is also assigned
   # to $o in the calling function, the object's scope is still
   # valid, and therefore nothing happens so the object
   $x = 1;
}

sub test() {
   my $o = new TestObject();

   # here we pass a copy of a reference to the object in $o
   test2($o);

   # this will print out "ok\n", because the object is still
   # valid and the member has been set by test2()
   if ($o.member == "tree")
      print("ok\n");
}
# when test() exits, the object in $o will go out of scope
# and be deleted

If, however, an object is passed by reference, then the local variable of the called function that accepts the object owns the scope reference of the calling functions's variable.

An example:

sub test2($x) {
   # we can modify the original object like so:
   $x.member = "tree";

   # here we re-assign $x, and since we own the only scope 
   # reference to the object, the object will go out of 
   # scope here and be deleted
   $x = 1;
}

sub test() {
   my $o = new TestObject();

   # here we pass a reference to the object in $o
   test2(\$o);

   # the object has already been deleted in test2() and
   # therefore nothing will be printed out
   if ($o.member == "tree")
      print("ok\n");
}

2.19.8. Object Scope

Objects are automatically deleted when their scope-relevant reference count reaches zero (note that objects can be deleted manually at any time by using the delete statement). Whenever an object is deleted, the object's class' destructor method is run on the object.

The following affect objects' scope:

  • Variable Assignments

    An object's automatic scope is prolonged as long as the object is assigned to a local variable.

  • Existence of a Closure Created Within the Object

    Any closures created from within the object encapsulate the object's state (along with any local variables referenced within the closure) and also prolong the object's automatic scope as long as the closure exists.

  • Object Method Thread Launched Within the Object

    If a member function thread was launched from within the object using the background operator, the object's automatic scope is prolonged to the life of the new thread. Object threads started externally to the object (i.e. not directly from an expression with the background operator within a method) will not prolong the scope of the object.

    If an object with running threads is explicitly deleted, and this case is not handled in the object's destructor() method (by ensuring that all other running threads terminate gracefully), exceptions will be thrown in other threads at any attempt to access the already-deleted object.

    For more information about threading, please see the following section Threading

Note

The fact that object threads and closures can prolong object scope means, for example, that objects assigned to local variables can exist for longer than the scope of their host variable if they have one or more methods running in other threads or if closures created from within the object still exist at the time the local variable goes out of scope.

2.19.9. Copying Objects

To explicitly generate a copy of an object, the copy() constructor must be called. This is a special method that exists implicitly for every class even if it is not explicitly defined (like constructor() and destructor() methods). The implicit behavior of the copy() constructor is to create a new object with new members that are copies of the original members (except objects are once again referenced). Then, if any copy() method exists, it will be executed in the new object, passing a reference to the old object as the first paramter.

Note

In a class hierarchy copy() methods are called in the same order as constructor() methods.

Note

Not all built-in classes can be copied. Classes not supporting copying will throw an exception when the copy() methods are called. See the documentation for each class for more information.