Instantiation

Dear Computer

Chapter 5: Objects

Instantiation

A playwright may introduce only so many characters before the audience gets overwhelmed. Maybe four? Eight? An object-oriented programmer, on the other hand, brings hordes of objects onto the stage in the blink of an eye. Whereas the characters in a play must be distinct from one another, objects tend to be mass-produced. They are manufactured en masse by treating a class as a blueprint.

New Semantics

To produce an object, which is an instance of a class, we must instantiate it. In many languages, the new operator followed by a call to a constructor produces an instance, as in this C++ code:

C++
Image *image = new Image(480, 320, Image::GRAYSCALE);

In Ruby, new is a static method:

Ruby
image = Image.new(480, 320, Image::GRAYSCALE)

The new operator or method has two-fold semantics:

C++, Java, Ruby, and JavaScript all require an explicit new. Languages that prioritize brevity of expression, including Python and Kotlin, omit new from instantiations but still have its semantics. There's no new in this Kotlin instantiation:

Kotlin
val image = Image(480, 320, Image.GRAYSCALE)

C has no objects and therefore no new operator. But it does have structs, and we can write a function that initializes a struct as a constructor would. The work of new is emulated with two separate statements in this C program:

C
struct image_t *image = (struct image_t *) malloc(sizeof(image_t));
initialize_image(image, 480, 320, GRAYSCALE);

The first statement allocates memory, and the second initializes that memory. In a language with the two-fold semantics of new, there's no danger of forgetting one of these steps like there is in C.

Memory

Recall that the stack is where a function's local variables and parameters are stored, and the heap is where data that lives beyond a function's lifetime is stored. The new operation places objects on the heap. That seems unnecessarily strict. Why can't objects be placed on the stack? When a function finishes executing, its memory is wiped. If the function returns an object stored on the stack, then the object must be copied into the caller to avoid being lost. Objects are potentially much larger than primitives, so copying them has a higher cost. Many languages eliminate this cost of copying by forcing all objects to live on the heap. Returning a heap object is cheap because only a pointer is copied.

C++ does allow objects to be allocated on the stack. A new stack object is declared and initialized with simpler syntax than a typical assignment:

C++
Image image(480, 320, Image::GRAYSCALE);

Note that there's not an even assignment operator in this declaration even though it's performing an initialization. This object can be passed cheaply to called functions as a reference or a pointer. If passed as a value, the copy may be expensive.

Singletons

Classes permit many instances, but some objects are one of a kind. An object of which there is only a single instance is called a singleton. For example, suppose we want to issue logging messages from anywhere in a project, and we want all the messages to funnel into a single output stream. We want a singleton Logger object that any function may access, but we don't want any other Logger objects floating around and getting in the way.

We can create our own singleton using a class with a static variable initialized to hold the single instance. By making the constructor private, no other entity may create a second instance. This Java class defines a singleton Logger:

Java
public class Logger {
  private static final SINGLETON = new Logger();

  private Logger() {/* ... */}

  private void warn(String message) {/* ... */}
  private void err(String message) {/* ... */}

  public static void warn(String message) {
    SINGLETON.warn(message);
  }

  public static void err(String message) {
    SINGLETON.err(message);
  }
}

The only way to log a message is by going through the static methods which delegate to the private singleton:

Java
Logger.warn("network lost");

Kotlin provides syntactic support for creating singletons in a simpler manner. A singleton is defined just like a class, only the keyword object is used instead of class:

Kotlin
object Logger {
  fun warn(message: String) {
    println("Warning: $message")
  }

  fun err(message: String) {
    println("Error: $message")
  }
}

There's no static variable to act as the receiver. Instead, the singleton's methods are invoked as if they were static:

Kotlin
fun main() {
  Logger.warn("network lost")
}

No constructor is ever explicitly called. The singleton is implicitly instantiated upon its first access.

← Object SyntaxProperties →