CPSC 427: Object-Oriented Programming
Michael J. Fischer
Singleton Design Pattern (revisited)
Another
version
of
Serial
In demo 20b-SmartPointer, we used the singleton design pattern to
create class Serial to serve as a UID generator.
To review, a public static function uidGen() returns a pointer to a
newly created instance of Serial the first time it is called, and it saves
that pointer in a private static variable Sobj.
Subsequent calls to uidGen() simply return the saved pointer.
Because the constructor is private, no other instantiations are
possible.
The instance defines a public operator(), making it a functor which can be called to return the next UID.
Drawbacks to this implementation
The primary drawback to the implementation of Serial in 20b-SmartPointer is that the client must do two steps to get the next UID:
In the SPtr example, we confusingly called the instance pointer uidGen
so we could write uidGen() to get the next UID.
By choosing to store the pointer uidGen as a data member of SPtr, we incur the storage cost on every instance of SPtr.
A streamlined UID generator
In demo 21a-SmartPointer, we improve the implementation of Serial
so that there only a single public static functionnextID() that the client
must call to get the next UID, e.g.,
To do this, the code is turned around. uidGen() becomes private, and
the new public static function newID() replaces the old operator()
extension.
Now newID() calls uidGen() each time it is called to get the instance pointer, which it then uses to access the private data member nextUID.
Serial.hpp, version 2
More on Functions
Functional
composition
Functional composition refers to using the result returned by one
function as the argument for another.
Example: g(f(x)).
The type of f(x) (which is the result type declared in the definition of
f()) must be compatible with the corresponding parameter type for
some method of g().
Types are compatible if they are the same, or if the result type can be converted to the corresponding parameter type.
Type compatibility
Here’s what the compiler does when it sees the call g(f(x)).
Calling constructors implicitly
Normally, constructors are called implicitly when an object is created,
whether by new (in the case of dynamic storage) or by having a
declaration executed (in the case of automatic storage).
When several constructor methods are present, which is chosen depends on the arguments supplied, either explicity or through ctors, but the call itself is implicit.
Examples
Calling constructors explicitly
Constructors can also be called explicitly, just like ordinary global
functions.
The meaning is to create a new temporary stack object, just as a new
temporary is created to hold the result of y+z in the expression
x*(y+z).
As with all object construction, the constructor is called when the object
is created, and the destructor is called when it is deleted.
Because the created object is temporary, it must be used immediately,
after which it will be discarded.
This is how throw Fatal("Error message") works. Fatal() creates an exception object of type Fatal for use by throw.
Conversion using constructor
Now suppose f() returns an object of type A& and g() expects an
argument of type B. What happens with g(f())?
Example 1:
Compiler will use B’s constructor to build a B& from an A&.
Output is “B constructor called”.
Conversion using a cast
Example 2:
Compiler will use A::operator B() to cast the A& returned by f() to the B expected by g().
Output is “operator B cast called”.
What if both options exist?
Compiler will complain “error: conversion from ’A’ to ’B’ is ambiguous”.
Casts and Conversions
Casts
in
C
A C cast changes an expression of one type into another.
Examples:
Different kinds of casts C uses the same syntax for different kinds of casts. Value casts convert from one representation to another, partially preserving semantics. Often called conversions.
C++ casts
C++ has four kinds of casts.
Explicit cast syntax
C++ supports three syntax patterns for explicit casts.
Implicit casts
General rule for implicit casts: If a type A expression appears in a context where a type B expression is needed, use a semantically safe cast to convert from A to B.
Examples:
Ambiguity
Can be more than one way to cast from B to A.
Comment from g++: conversion from ’B’ to ’A’ is ambiguous
Comment from clang++: error: reference initialization of type
’A &&’ with initializer of type ’B’ is ambiguous
explicit keyword
Not always desirable for constructor to be called implicitly.
Use explicit keyword to inhibit implicit calls.
Previous example compiles fine with use of explicit:
Question: Why was an explicit definition of the default constructor not needed?