C++ Training Datascope Lawrence D’Antonio
description
Transcript of C++ Training Datascope Lawrence D’Antonio
C++ TrainingC++ TrainingDatascopeDatascope
Lawrence D’AntonioLawrence D’Antonio
Lecture 9Lecture 9
An Overview of C++:An Overview of C++:
What is Typing?What is Typing?
Type Systems
“The purpose of a type system is to prevent the occurrence of execution errors during the running of a program.” Cardelli
The largest allowed range of values for a program variable is its type.
Languages in which variables have non-trivial types are called typed languages.
Type Systems 2
Languages that do not restrict the ranges of variables are called untyped languages.
The -calculus is an example of an untyped language.
Languages can check for type errors either during compile time (static checking) or run-time (dynamic checking).
Type Systems 3
There are two types of execution errors. Errors that cause computation to stop are
called trapped errors. Errors that go unnoticed and cause
arbitrary behavior are called untrapped errors.
A program is safe if it does not cause untrapped errors to occur.
Type Systems 4
Languages in which programs are safe are called safe languages.
Typed Untyped
Safe ML, Java LISP
Unsafe C Assembler
Types of typingTypes of typing
Static typing: Data type determined at compile-time. Type must be declared or inferred.
Dynamic typing: Data type may be determined at run-time. Type need not be declared.
Strong typing: Variables are bound to a specific type.
Weak typing: A variable’s type may change.
Types of typing 2Types of typing 2
Soft typing: Type checking occurs, but incorrect types are only given warnings.
Manifest typing: Types are named explicitly in the code.
Varieties of typing
Static and strong typing: Java, Pascal, OCaml, Haskell
Static and weak typing: C/C++ Dynamic and strong typing: Python Dynamic and weak typing: PHP
Type Systems
Is there a nontrivial type associated with each declaration?
Static typing = yes, Dynamic typing = no, Soft typing = optional
If there is, are these types declared explicitly in the source code?
Manifest typing = yes, Type Inference = optional
Type Systems 2
Does the possibility of a type failure cause a compile-time error?
Static typing = yes, Dynamic or soft typing = no
Is the type system strictly enforced, with no loopholes or unsafe casts?
Strongly typed = yes, Weak typing = no
Benefits of a Type System
Safety: The use of a type system allows the compiler to detect invalid or meaningless code. For example, the code
x = 5/”Hello”
will be caught as illegal.
Benefits of a Type System 2
Optimization: a compiler can use type information in various ways to improve a program.
For example, knowing values of a certain type must align at a multiple of 4 may let the compiler use more efficient machine instructions.
Also, a type system allows the compiler to select appropriate code.
Benefits of a Type System 3
Documentation: the use of type can be documentation of the programmer’s intent.
Type annotation documents how program objects are to be used.
Benefits of a Type System 4
Abstraction: the ability to name types allows the programmer to think about programs at a higher level.
The hiding of type details lets the programmer directly model the problem domain.
The correctness of a program does not depend on the implementation of types.
Static typing
Also known as early binding. Under static typing, the method to be
called is the one associated with the type of the formal parameter.
The binding can occur as soon as the type of the formal parameter is known.
C++ uses static typing by default.
Static typing 2
A reference value is a program construct that is a value and can have a value. It can have different values at different times.
In static typing a reference is constrained with respect to the type of value denoted by the reference.
In a statically typed language one relies upon the compiler to do type checking.
Static typing 3
There are two ways to implement static typing.
One method is type inference. Here the type of an expression is inferred through analysis of the program.
Another method is manifest typing. Here objects must be declared with a type annotation.
Type inference
Many languages use type inference exclusively or in part. For example, the languages Boo, C# 3.0, Cayenne, Chrome, Clean, Cobra, D, Epigram, F#, Haskell, ML, Nemerle, OCaml, Scala use type inference.
Type inference 2
C# 3.0 example
var x = “Hello”; //x is deduced to be a string
//More complex example
var custQuery = from cust in customers where cust.City == "Phoenix"
select new { cust.Name, cust.Phone };
Type inference 3
Boo example. The type of an array is the least generic type that could store all elements in the array.
a = (1, 2) # a is of type (int)
b = (1L, 2) # b is of type (long)
c = ("foo", 2) # c is of type (object)
Type inference 4
Haskell example apply f v = f v The inferred type for apply is (a -> b) -> a -> b Namely apply is a function that takes a
function taking an argument of type a and returning a value of type b and applies the function to the argument, returning a b.
Type inference 5
C++ uses type inference.
For example, template parameters are deduced from the argument types in a function call.
Type inference 6
C++ ‘09 will have a greater use of type inference. For example:
for(auto p = v.begin();
p! = v.end(); ++p)
cout << *p << endl;
Type inference 7
Chrome example
var u := from u in lUsers where u.Age = 35 order by u.Name;
Here lUsers is a collection of objects of unnamed type.
Type inference 8
Cobra exampleclass Foo def bar
i = 1_000_000 for j = 0 .. i
doSomething(j)
def doSomething(i as int) pass
Type inference 9
F# examplelet asynctask = async {
let req = WebRequest.Create(url)
let! response = req.GetResponseAsync()
use stream = response.GetResponseStream()
use streamreader = new
System.IO.StreamReader(stream)
return streamreader.ReadToEnd()
}
Type inference 10
Nemerle example
def d = Dictionary ();
d.Add ("Ala", 7);
foreach (s in args) { ... }
Dynamic typing
In dynamic typing, type checking normally occurs at run time.
Operations are checked just before they are performed.
For example, the code for the + operator may check the types of its operands just before the addition is performed.
Dynamic typing 2
For example, if the operands for the + operator are both integers then integer addition is performed.
If one operand is an integer and the other a floating point number then floating point addition is performed.
If one operand is an integer and the other a string then an exception is raised.
Dynamic typing 3
Dynamically typed languages are more flexible than statically typed languages.
The problem for statically determining for an arbitrary program whether or not a type error will occur at run time is undecidable.
Therefore sound static type checkers will determine some programs as potentially unsafe that would actually execute without a type error.
Dynamic typing 4
Static typing finds type errors at compile time.
Advocates of strongly statically typed languages such as ML and Haskell claim that most program errors are type errors. Namely, most errors would not occur if types were used in the correct manner by the programmer.
Dynamic typing 5
Static typing usually results in compiled code that executes more quickly than dynamic typing.
On the other hand, dynamic typing can reduce compile time and speed up the software development cycle.
Dynamic typing 6
Metaprogramming is the ability of a computer program to use or manipulate other programs (including itself) as its data.
Dynamic typing usually makes metaprogramming easier. For example, templates in C++ are more cumbersome than equivalent code in Python or Ruby.
Metaprogramming
Metaprogramming is a facility provided by many languages.
Programs in Lisp, Python, Ruby, Smalltalk, PHP, REBOL, Perl, Tcl, Lua, and JavaScript are modifiable at run time.
The language of a metaprogram is called its metalanguage.
Metaprogramming 4
The ability of a programming language to be its own metalanguage is called reflection.
Other types of metaprogramming include: Generative programming which involves
one program generating another. A quine which is a program that outputs
itself.
Metaprogramming 5
Forth which is a self-compiling language. The programmer can modify the compiler.
A compiler is an example of a metaprogramming tool for translating high-level programs into machine code.
The compiler-compiler yacc is a metaprogramming tool to generate a tool for translating high level programs into machine code.
Metaprogramming 6
A quine in Atlas Autocode%BEGIN
!THIS IS A SELF-REPRODUCING PROGRAM
%ROUTINESPEC R
R
PRINT SYMBOL(39)
R
PRINT SYMBOL(39)
NEWLINE
%CAPTION %END~
%CAPTION %ENDOFPROGRAM~
Metaprogramming 7 Quine continued
%ROUTINE R %PRINTTEXT ' %BEGIN !THIS IS A SELF-REPRODUCING PROGRAM %ROUTINESPEC R R PRINT SYMBOL(39) R PRINT SYMBOL(39) NEWLINE %CAPTION %END~ %CAPTION %ENDOFPROGRAM~ %ROUTINE R %PRINTTEXT ' %END %ENDOFPROGRAM
Metaprogramming 8
Quine example in C#include <stdio.h>
int main(int argc, char** argv)
{
/* This macro B will expand to its argument, followed by a printf command that prints the macro invocation as a literal string */
#define B(x) x; printf(" B(" #x ")\n");
Metaprogramming 9
Quine C example continued/*This macro A will expand to a printf command that prints the macro invocation, followed by the macro argument itself. */
#define A(x) printf(" A(" #x ")\n"); x;
/* Now we call B on a command to print the text of the program up to this point. It will execute the command, and then cause itself to be printed. */
Metaprogramming 10
B(printf("#include <stdio.h>\n\nint main(int argc, char** argv)\n{\n/*This macro B will expand to its argument, followed by a printf\n command that prints the macro invocation as a literal string */\n#define B(x) x; printf(\" B(\" #x \")\\n\");\n\n/* This macro A will expand to a printf command that prints the macro invocation,\n followed by the macro argument itself. */\n#define A(x) printf(\" A(\" #x \")\\n\"); x;\n\n/* Now we call B on the text of the program\n up to this point. It will execute the command, and then cause\n itself to be printed. */\n"))
Metaprogramming 11
A(printf("/* Lastly, we call A on a command to print the remainder of the program;\n it will cause itself to be printed, and then execute the command. */\n}\n")) /* Lastly, we call A on a command to print the remainder of the program; it will cause itself to be printed, and then execute the command. */ }
Metaprogramming 12
An extreme example of metaprogramming is language-oriented programming.
To solve a problem using language-oriented programming, one doesn’t use a general-purpose language.
Instead the programmer creates a domain specific programming language.
Metaprogramming 13
Two types of metaprogramming.
One: expose the internals of the run time engine through APIs.
Two: dynamic execution of strings containing program commands.
Metaprogramming 14
Metaprogramming example (in bash).#!/bin/bash
# metaprogram
echo '#!/bin/bash' >program
for ((I=1; I<=992; I++)) do
echo "echo $I" >>program
done
chmod +x program
Ruby Example
Suppose we want to read a CSV file “people.txt”
name,age,weight,height "Smith, John", 35, 175, "5'10" "Ford, Anne", 49, 142, "5'4" "Taylor, Burt", 55, 173, "5'10" "Zubrin, Candace", 23, 133, "5'6"
Ruby Example 2
# file: my-csv.rb class DataRecord
def self.make(file_name) data = File.new(file_name) header = data.gets.chomp data.close class_name =
File.basename(file_name,".txt").capitalize # "foo.txt" => "Foo" klass = Object.const_set(class_name,Class.new) names = header.split(",")
Ruby Example 3
klass.class_eval do
attr_accessor *names
define_method(:initialize) do |*values|
names.each_with_index do |name,i|
instance_variable_set("@"+name, values[i]) end
end
Ruby Example 4
Still inside klass.class_eval
define_method(:to_s) do
str = "<#{self.class}:"
names.each
{|name| str << "#{name}=#{self.send(name)}" }
str + ">"
end
alias_method :inspect, :to_s
end
Ruby Example 5
def klass.read
array = []
data = File.new(self.to_s.downcase+".txt") data.gets # throw away header
data.each do |line|
line.chomp!
values = eval("[#{line}]")
array << self.new(*values)
end
data.close array
end
Ruby Example 6
Driver program
require 'my-csv'
DataRecord.make("people.txt") # Ignore return value list = People.read # refer to the class by name
puts list[0]
# Output:
# <People: name=Smith, John age=35 weight=175 height=5'10>
Ruby Example 7
Attributes are first-class citizensperson = list[0] puts person.name # Smith, John if person.age < 18 puts "under 18"
else puts "over 18" # over 18
end kg = person.weight / 2.2 # kilograms
Dynamic typing example# Python example
class Cat: def speak(self): print "meow!"
class Dog: def speak(self): print "woof!"
class Bob: def speak(self): print "hello world!"
def command(pet): pet.speak()
pets = [ Cat(), Dog(), Bob() ]
for pet in pets: command(pet)
Strongly typed
Different definitions of strongly typed. A language is strongly typed if: type annotations are associated with
variable names, rather than with values. If types are attached to values, it is weakly typed.
it contains compile-time checks for type constraint violations. If checking is deferred to run time, it is weakly typed.
Strongly typed 2
A language is strongly typed if: there are compile-time or run-time checks
for type constraint violations. If no checking is done, it is weakly typed.
conversions between different types are forbidden. If such conversions are allowed, it is weakly typed.
Strongly typed 3
A language is strongly typed if: conversions between different types must
be indicated explicitly. If implicit conversions are performed, it is weakly typed.
there is no language-level way to disable or evade the type system. If there are casts or other type-evasive mechanisms, it is weakly typed.
Strongly typed 4
A language is strongly typed if: it has a complex, fine-grained type system
with compound types. If it has only a few types, or only scalar types, it is weakly typed.
the type of its data objects is fixed and does not vary over the lifetime of the object. If the type of a datum can change, the language is weakly typed.
Strongly typed 5
A language is strongly typed if: The language implementation is required
to provide a type checker that ensures that no type errors will occur at run time.
For example, the types of operands are checked in order validate an operation.
Is this legal?
main() { //Converts float to string of 4 charsunsigned char *c; float f = 10;
for (c = (char *)&f; c < sizeof(float) + (char *)&f; c++)
{ std::cout << *c;} std::cout<< ‘\n’; return 0;
}
No this is not considered legal by the compiler. There is a type error.
type.cpp:8: error: invalid conversion from `char*' to `unsigned char*'type.cpp:8: error: comparison between distinct pointer types `unsigned char*' and `char*' lacks a cast
The compiler is complaining about the comparison:
c < sizeof(float) + (char *)&f
Answer part 1
Answer part 2
The compiler is saying that the left side is unsigned char *, but the right side is char *. Hence the type error.
Why is this an error?
The sizes of char and unsigned char are the same. So it should be safe to compare pointers to these types.
Answer part 3
Why not give a warning instead of an error?
More importantly. The compiler balks at the pointer comparison, but says nothing about the assignment
c = (char*)&f
It would seem that both expressions should be treated the same.
Answer part 4
But if you change to
for (c = (char *)&f; c < sizeof(float) + (unsigned char *)&f; c++) { std::cout << *c; }
The compiler is then happy with the comparison, but states “invalid conversion from `char*' to `unsigned char*‘ “ about the assignment c = (char *)&f.
Answer part 5
If you change the example to:
unsigned char *c;
float f = 10;
for (c = (unsigned char *)&f;
c < sizeof(float) + (unsigned char *)&f;
c++) { std::cout << *c; }
Answer part 6
Then the compiler is happy and the program has the output: A
If you use float f = 100340.567; instead, then the output is: GÃúI
Answer part 7
Note: it would be a type error to compare char and unsigned char.
This is an example of the conservative nature of static typing.
All in all, this example illustrates strong and weak typing.
Duck typing
“If it looks like a duck and quacks like a duck then it’s a duck.”
In dynamically typed languages such as Ruby and Python, polymorphism can be achieved without the use of inheritance.
Duck typing 2
Pseudo-code example:function calculate(a, b, c) =>
return (a+b)*c a = calculate(1, 2, 3) b = calculate([1, 2, 3], [4, 5, 6], 2) c = calculate('apples ', 'and oranges, ', 3)
print to_string a print to_string b print to_string c
Duck typing 3
This example illustrates the use of polymorphism without inheritance. It works for any types such as int, list, string so long as the types support + and *The output is:
9
[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
apples and oranges, apples and oranges, apples and oranges,
Duck typing 4
How would you do this example in C++?
Would you use inheritance or templates?
Duck typing 5
template<class T, class U>
T calculate(const T &a, const T &b,
const U &c)
{
return (a+b)*c;
}
Weak typing example
var x := 5;
var y := "37";
Print(x + y);
In Visual Basic this prints: 42
In JavaScript this prints: 537
Type and Class Hierarchies
There are four kinds of inheritance: Substitution inheritance Inclusion inheritance Constraint inheritance Specialization inheritance
Substitution inheritance
We say that a type t inherits from a type t' if we can perform more operations on objects of type t than on objects of type t'.
This means that every place an object of type t' occurs we can substitute for it an object of type t.
Substitution inheritance 2
This means that objects of type t extend objects of type t'.
Open-Closed Principle
From Bertran Meyer,
“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.”
Open-Closed Principle 2
“Open for extension” means that the behavior of an object can be extended. The object may be made to behave in new and different ways as the requirements of the application change or to meet the needs of a new application.
Open-Closed Principle 3
“Closed for modification” means that the source code of the object cannot change.
Programs conforming to the open-closed principle are modified by adding code rather than changing existing code.
Open-Closed Principle 4
Suppose we have a Shape class hierarchy. Let us define a function:
void DrawAllShapes(set<Shape*> &s) {
std::set<Shape*>::iterator p;
for(p = s.begin(); p != s.end(); p++)
(*p)->draw();
}
Open-Closed Principle 5
The DrawAllShapes function satisfies the open-closed principle. If additional shapes were added to the Shape hierarchy this function would not need to be modified.
On the other hand, if instead, type fields had been used then the function would violate the principle (additional shapes would require new cases in the code).
LSP
The Liskov Substitution Principle states
“If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.”
LSP 2
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
A function that violates LSP will also violate the Open-Closed Principle.
LSP 3
class Rectangle{public:void SetWidth(double w) {itsWidth = w;}void SetHeight(double h) {itsHeight = h;}double GetHeight() const {return itsHeight;}double GetWidth() const {return itsWidth;}
private:double itsWidth;double itsHeight;
};
LSP 4
What if we want to define a Square class that is derived from Rectangle?
The Square inherits the width and height data members.
The Square doesn’t need data members for both width and height. But it inherits both.
One must write code that keeps the height and width equal.
LSP 5class Square : public Rectangle{public:
void SetWidth(double w);void SetHeight(double h);
};
void Square::SetWidth(double w){
Rectangle::SetWidth(w); Rectangle::SetHeight(w);}
void Square::SetHeight(double h){
Rectangle::SetHeight(h); Rectangle::SetWidth(h);}
LSP 6
Now the Square remains mathematically valid.
Square s;
s.SetWidth(1); //Sets width & height to 1
s.SetHeight(2); //Sets width & height to 2
LSP 7
But what happens with the following code?
void f(Rectangle& r){
r.SetWidth(32);}//...Square s;s.SetHeight(3);f(s);
LSP 8
Bad things happen with this code!
Square s;s.SetHeight(3)f(s); After this code executes, Square s has
height = 3 and width = 32. So it’s no longer a Square!
LSP 9
Of course the problem is that SetHeight() and SetWidth() are not virtual in Rectangle.
We can change Rectangle so as to make these functions virtual, but then the creation of the child (the Square) causes a change in the parent (the Rectangle).
LSP 10
This violates the Open-Closed Principle. Rectangle should be open for extension (Square) but not for modification.
Perhaps these functions should have been made virtual when Rectangle was defined?
But that would have required that Rectangle anticipated Square.
LSP 11
Change Rectangle as shown below, but leave Square unchanged.
class Rectangle{public:
virtual void SetWidth(double w) {itsWidth = w;}virtual void SetHeight(double h) {itsHeight = w;}double GetHeight() const {return itsHeight;}double GetWidth() const {return itsWidth;}
private:double itsWidth;double itsHeight;
};
LSP 12
We now seem to have a self consistent model of the concepts of rectangle and square.
Whatever you do to a Square object, it behaves like a square.
Whatever you do to a Rectangle object it behaves like a rectangle.
LSP 13
But a model that is self consistent is not necessarily consistent from the point of view of clients.
The following function illustrates a violation of the Liskov Substitution Principle and shows the fundamental flaw in the design.
LSP 14
void g(Rectangle& r)
{
r.SetWidth(5);
r.SetHeight(4);
assert(r.GetWidth() * r.GetHeight()
== 20);
}
LSP 15
What’s wrong with this function? If it is passed a Rectangle then the
assertion will be true. If it is passed a Square then the assertion
will be false. Was the programmer who wrote that
function justified in assuming that changing the width of a Rectangle leaves its height unchanged?
LSP 16
The assumption seems reasonable considering the nature of rectangles.
So there are functions that take pointers or references to Rectangle objects, but cannot operate properly upon Square objects.
This is a violation of LSP. One cannot substitute a Square object for a Rectangle in function g().
LSP 17
A model, viewed in isolation, can not be meaningfully validated. The validity of a model can only be expressed in terms of its clients.
Square and Rectangle are self consistent but when we looked at them from the viewpoint of a programmer who made reasonable assumptions about the base class, the model broke down.
LSP 18
What went wrong? Isn’t a Square a type of Rectangle?
No! The behavior of a Square object is not consistent with the behavior of a Rectangle object.
The is-a relationship pertains to extrinsic public behavior ; behavior that clients may depend on.
LSP 19
For example, the function g() defined previously depends on the height and width varying independently of one another.
In order for the LSP to hold, all derived classes must conform to the behavior that clients expect of the base classes that they use.
LSP 20
Relationship between LSP and Design by Contract.
In Design by Contract, methods declare pre- and post-conditions.
Preconditions must be true in order for the method to execute.
Upon completion, the method guarantees that the postcondition will be true.
LSP 21
Bertrand Meyer: “When redefining a routine [in a derived class], you may only replace its precondition by a weaker one, and its postcondition by a stronger one.”
LSP 22
In the example, Rectangle::SetWidth() has the postcondition that the height of the rectangle is unchanged.
While Square::SetWidth() has replaced that postcondtion with one guaranteeing that the width and height are equal.
Square::SetWidth() has violated the contract of the base class.
Inclusion inheritance
Corresponds to the concept of classification.
It states that t is subtype of t ', if every object of type t is also an object of type t '. This type of inheritance is based on structure and not on operations.
Constraint inheritance
A type t is a subtype of a type t ', if it consists of all objects of type t which satisfy a given constraint. An example of such a inheritance is that teenager is a subclass of person: teenagers don't have any more fields or operations than persons but they obey more specific constraints (their age is restricted to be between 13 and 19).
Specialization inheritance
A type t is a subtype of a type t ', if objects of type t are objects of type t which contains more specific information. Examples of such are employees and managers where the information for managers is that of employees together with extra fields.
Type vs. Class
Classes are templates for creating objects, providing initial values for instance variables and the bodies for methods.
Types are abstractions that represent sets of values and the operations and relations applicable to them.
Type vs. Class 2
Types should hide implementation details. Instead they should only reveal the names
and signatures of the messages that may be sent to them.
Distinct classes with the same public methods and types generate objects of the same type.
Type vs. Class 3
Here is an example
class A {private: std::string s; int n;public: A(const std::string &str):
s(str), n(str.size()) {} void foo(int a) { n += a; s.resize(n); } std::string get() { std::cout << s.size() << '\n'; return s; }};
Type vs. Class 4class B {private: std::string t; bool f() { return t == "Hello"; }public: B(const std::string &s): t(s) { if (f()) std::cout << "World\n"; } void foo(int a) { std::string temp(t); for(int i = 0; i < a; i++) t += temp; } std::string get() { return t; }};
Type vs. Class 5
main(){ A a("World"); B b("Hello"); a.foo(2); std::cout << a.get() << '\n'; b.foo(3); std::cout << b.get() << '\n'; return 0;}
Type vs. Class 6
Objects of classes A and B are the same type.
Objects of different classes may be used interchangeably and simultaneously as long as they have the same object type.
Type vs. Class 7
We say that T is a subtype of U if a value of type T can be used in any context in which a value of type U is expected.
A value of type T can masquerade as an element of type U in all contexts.
A subclass may be defined by either adding or modifying methods and instance variables of the original type.
Type vs. Class 8
Subtyping depends only on the types or interfaces of values, while inheritance depends on implementations.
A subtype is not necessarily a subclass. If an object of type T has at least all of the
methods of type U, and the corresponding methods have the same type, then an object of type T can masquerade as an object of type U.
Type vs. Class 9
Class defines structure, while type abstracts a similarity.
For example, Benjamin Franklin and Ohio are two classes of submarines.
Both classes are the same type of submarine, namely, they are both ballistic missile submarines.
Type vs. Class 10
In STL, a fundamental concept is that of a Container.
Sequence Container and Associative Container are types of Container.
Type vs. Class 11
But there are no C++ classes called Sequence Container and Associative Container.
In STL, vector, list, and deque are Sequence Container classes.
They implement a specific form of Sequence Container.
Is this legal?
class A {};
class B: public A {};
Example part 2
class C {public: virtual A foo() {
std::cout << "A::foo()\n";return A(); }
};
class D: public C {public: int foo() {
std::cout << "B::foo()\n"; return 5; }
};
Example part 3
main() { C *p = new C; p->foo();
p = new D; p->foo(); return 0;}
Not legal!
conflicting return type specified for `virtual int D::foo()‘ overriding `virtual A C::foo()'
Violation on rules for covariant return types in C++.
Is this legal?
class C {public: virtual B foo() {
std::cout << "A::foo()\n";return B(); }
};
class D: public C {public: A foo() {
std::cout << "B::foo()\n"; return A(); }
};
Not legal!
invalid covariant return type for `virtual A D::foo()' overriding `virtual B C::foo()'
Again, incorrect return type for overriding function.
Covariant types
A covariant operator preserves the ordering of types.
For example, array types are covariant.
So that if T is a subtype of U then Array[T] is a subtype of Array[U].
Covariant types 2
In C++, return types are covariant.
Namely, if Base::foo() is virtual then the return type of Der::foo() must be a subtype of Base::foo().
The derived class can only narrow return types, it cannot otherwise change them.
Is this legal?
class C {public: virtual A foo() {
std::cout << "A::foo()\n";return A(); }
};
class D: public C {public: B foo() {
std::cout << "B::foo()\n"; return B(); }
};
Not legal!
invalid covariant return type for `virtual B D::foo()‘ overriding `virtual A C::foo()'
Why is this? Isn’t B a subtype of A?
Not for return by value.
Is this legal?
class C {public: virtual A* foo() {
std::cout << "A::foo()\n";return new A(); }
};
class D: public C {public: B* foo() {
std::cout << "B::foo()\n"; return new B(); }
};
Yes this is legal.
This is a valid use of covariant return types.
Contravariant types
A contravariant operator reverses the order of types.
For example, argument types are contravariant.
A function that is expecting a Base& may be passed a subtype but not a supertype.
Contravariant types 2
The general relationship between functions and types can be expressed:
If and then
This is called the contravariant rule.
1 2 1 2S S T T
2 2S T1 1T S
Contravariant types 3
This rules states that if T1 is a subtype of S1 and S2 a subtype of T2 then functions that take an argument of type S1 and return a value of type S2 are a subtype of functions that take an argument of type T1 and return a T2.
So functions are contravariant in parameter and covariant in return type.
Is this legal?
class A {};class B: public A {};
typedef void(*FPTR)(B*);
void foo1(B*) { std::cout << "foo1(B)\n"; }void foo2(A*) { std::cout << "foo2(A)\n"; }
void bar(FPTR g) { }
main() { bar(foo1); bar(foo2);}
Let’s analyze this code. Function bar() takes as its argument a function that takes a B* (and has a void return).
Function foo1() is this type of function.
So bar(foo1); should be legal.
Function foo2() takes an A* and so by the contravariant rule it should be possible to substitute foo2 for foo1 in any valid expression using foo1.
So bar(foo2); should be legal.
But…
The C++ compiler says that
bar(foo2); is illegal because
type6.cpp:16: error: invalid conversion from `void (*)(A*)' to `void (*)(B*)'
type6.cpp:16: error: initializing argument 1 of `void bar(void (*)(B*))'
But this is wrong!
It violates the contravariant rule and it violates common sense. Any argument which is valid to pass to a
void (*)(B*)
should also be valid to pass to a
void (*)(A*).
Hence, it should be legal to substitute foo2() for foo1().
Casts in C++
There are four different cast operators in C++
static_cast<>: handles conversions between related types.
reinterpret_cast<>: handles conversions between unrelated types.
dynamic_cast<>: performs a checked runtime conversion.
const_cast<>: throws away constness.
static_cast
A static cast converts between objects of related types.
For example, converting one pointer type to another, an enumeration to an integral type, or a floating-point type to an integral type.
Is this legal?
class A {};
class B: public A {public:void foo() { std::cout << "In foo()\n"; }
};
class C {};class D: virtual public C {};
Example part 2
main(){ int a = 4; float b = 6.5; int c =
static_cast<double>(a)/3;
int *p = static_cast<int*>(&b);
unsigned int *q = static_cast<unsigned int*>(&a);
Example part 3
const double PI = 3.14159;double d =
static_cast<double>(PI);const double cd =
static_cast<const double>(d);
double &rd = static_cast<double&>(PI);
Example part 4
A myA;
B myB;
A *pa = static_cast<A*>(&myB);
pa->foo();
B* pb = static_cast<B*>(&myA);
pb->foo();
Example part 5
C myC;
D myD;
D* pd = static_cast<D*>(&myC);
Let’s examine each static_cast.
int c = static_cast<double>(a)/3;
This is legal. Converts a/3 from integer into floating point division.
Note that the result then undergoes an implicit conversion back to int.
int *p = static_cast<int*>(&b);
This is not legal. The compiler says
error: invalid static_cast from type `float*' to type `int*'
unsigned int *q = static_cast<unsigned int*>(&a);
This is not legal. The compiler says
error: invalid static_cast from type `int*' to type `unsigned int*'
double d =static_cast<double>(PI);
This is legal. It is the same as: double d = PI;
const double cd = static_cast<const double>(d);
This is legal. It is the same as: const double cd = d;
double &rd = static_cast<double&>(PI);
This is not legal! The compiler says
error: invalid static_cast from type `const double' to type `double&‘
Note: it would be legal to declare
const double &rd = static_cast<const double&>(PI);
A *pa = static_cast<A*>(&myB);pa->foo();
The cast is legal. It’s just the usual conversion from derived class to base class.
But the function call pa->foo(); is illegal.
Class A has no member function foo().
B* pb = static_cast<B*>(&myA); pb->foo();
This is legal. You are allowed to cast a parent pointer to a child pointer. This is called an downcast.
The static_cast operator allows you to perform safe downcasts for non-polymorphic classes.
Note: the call pb->foo() is legal.
D* pd = static_cast<D*>(&myC);
Here C is a virtual base class of D. So this is illegal. The compiler says:
error: cannot convert from base `C' to derived type `D' via virtual base `C'
Is this legal?class A {};
class B: public A {private:
int x;public:
B(int a):x(a) {}void foo() { std::cout << "x = " << x << " in foo()\n"; }
};
B myB(5);B* pb = static_cast<B*>(&myA); pb->foo();
This is legal.
This is illegal. It all depends on your point of view.
The compiler accepts the code. But at run-time the following happens.
Bus error (core dumped)
This program has a type error. We are trying to print out a data member x that class A doesn’t have.
This shows that there are type errors in C++ not caught by the compiler.
Is this legal?class A {public:
virtual void foo() { std::cout << "In A::foo()\n"; }};
class B: public A {private: int x;public:
B(int a):x(a) {}void foo() { std::cout << "x = " << x << " in foo()\n"; }
};
B myB(5);B* pb = static_cast<B*>(&myA); pb->foo();
Now it’s legal. The output is
In A::foo()
Revised exampleclass A {public: void foo() { std::cout << "\nIn A::foo()\n"; } virtual void bar() { std::cout << "In A::bar()\n"; }};
class B: public A {private: int x;public: B(int a):x(a) {} void foo() { std::cout << "\nx = " << x << " in foo()\n" << "Address of object = " << this << '\n' << "Address of x = " << &x << '\n'; }};
Example 2
main(){
A myA; B myB(5); std::cout << "Size of A = " << sizeof(A) << '\n' << "Size of B = " << sizeof(B) << '\n';
myB.foo();
A *pa = static_cast<A*>(&myB); pa->foo();
Example 3
B* pb = static_cast<B*>(&myA);
pb->foo();
std::cout << "\nAddress of myA = "
<< &myA << '\n'
<< "Address of pb = "
<< pb << '\n';
}
Example 4 (output)Size of A = 4Size of B = 8
x = 5 in foo()Address of object = 0xffbff960Address of x = 0xffbff964
In A::foo()
x = 0 in foo()Address of object = 0xffbff968Address of x = 0xffbff96c
Address of myA = 0xffbff968Address of pb = 0xffbff968
Is this legal?
class A {public: void bar() { std::cout << "In bar()\n"; }};
class B: public A {public: void foo() { std::cout << "In foo()\n"; }};
Example part 2
A myA; B myB; void (B::* pmb)() = &B::foo; (myB.*pmb)(); void (A::* pma)() = &A::bar; (myA.*pma)();
Example part 3
pmb = &A::bar;
(myB.*pmb)();
pma = &B::foo;
(myA.*pma)();
void (B::* pmb)() = &B::foo;(myB.*pmb)();
This is legal. It first initializes a pointer to a member function of class B. Then the member function is called on object myB.
void (A::* pma)() = &A::bar;(myA.*pma)();
This is legal. It first initializes a pointer to a member function of class A. Then the member function is called on object myA.
pmb = &A::bar; (myB.*pmb)();
This is legal. You can safely convert a pointer to a member of the base class into a pointer to member of the derived class.
This is an instance of the contravariant rule.
pma = &B::foo; (myA.*pma)();
This is illegal, for good reason. You cannot convert a pointer to a member of the derived class to a pointer to a member of the base class.
Otherwise, you could call a member of the derived class from the parent.
Is this legal?
void (A::* pma)() =
static_cast<void (A::*)()>(&B::foo);
(myA.*pma)();
This is legal.
You can use static_cast to convert a pointer to a member function from one class to another class that are related by inheritance.
Is this legal?
class C { };
class D {public: void foo() { }};
C myC;void (C::*pmc)() = static_cast<void (C::*)()>(&D::foo);(myC.*pmc)();
No this is not legal.
You cannot cast a pointer to member between unrelated classes.
Summary of static_cast You can explicitly convert a pointer of a type A to a
pointer of a type B if A is a base class of B. If A is not a base class of B, a compiler error will result.
You may cast an lvalue of a type A to a type B& if the following are true: A is a base class of B You are able to convert a pointer of type A to a pointer
of type B The type B has the same or greater const or volatile
qualifiers than type A A is not a virtual base class of B
The result is an lvalue of type B.
Summary of static_cast 2
A pointer to member type can be explicitly converted into a different pointer to member type if both types are pointers to members of the same class. This form of explicit conversion may also take place if the pointer to member types are from separate classes, however one of the class types must be derived from the other.
Uses of static keyword
The keyword static is used to qualify a name in the following ways:
At file level: static means internal linkage. Namely, such names are local to the compilation unit.
Uses of static keyword 2
At the function level: static applied to a local variable in a function means a single, statically allocated object will be used to represent that variable in all calls to the function.
Uses of static keyword 3
At the class level: static applied to a class member means that one copy of that member is shared by all objects of that class. A static member function doesn’t need to be invoked by a particular object of that class.
Is this legal?
#include<iostream>
namespace { void foo()
{ std::cout << "foo\n"; }}
main() { foo(); return 0;}
Yes, it is legal. It is an example of an unnamed namespace.
Unnamed namespaces have an assumed using directive for the file it is defined in.
Since the namespace is unnamed, its members cannot be used in other files.
So that an unnamed namespace defines internal linkage. That means unnamed namespaces can be used in place of the keyword static at the file level.
reinterpret_cast
The reinterpret_cast is used to perform conversions between two unrelated types. The result of the conversion is usually implementation dependent and, therefore, not likely to be portable. You should use this type of cast only when absolutely necessary.
Is this legal?class A { };class B: public A {public: void foo() { std::cout << "In foo()\n"; }};
class C { };class D: virtual public C { };
class E { };class F {public: void foo() { std::cout << "In F::foo\n"; }};
Example part 2
main(){ int a = 4; float b = 6.5;
int *p = reinterpret_cast<int*>(&b); std::cout << *p << '\n'; unsigned int *q =
reinterpret_cast<unsigned int*>(&a); std::cout << *q << '\n';
Example part 3
C myC;D myD;
D* pd = reinterpret_cast<D*>(&myC);
E e;void (E::*pme)() =
reinterpret_cast<void (E::*)()>(&F::foo);(e.*pme)();
All of the casts are legal, since reinterpret_cast can convert one pointer type into another.
The output of the program is:
10873733124In F::foo
Is this legal?
What if we add the following to the previous example?
int x = reinterpret_cast<int>(myD);
Not legal. Can’t use reinterpret_cast to do arbitrary conversions.
Example
// Returns a hash code based on an address
unsigned short Hash( void *p ) {
unsigned int val = reinterpret_cast<unsigned int>( p );
return ( unsigned short )( val ^ (val >> 16));
}
main() {
int a[20];
for ( int i = 0; i < 20; i++ )
cout << Hash( a + i ) << endl;
}
Summary of reinterpret_cast
Can convert between any pointer types.
Can convert between pointer type and integral type.
Cannot cast away constness.
dynamic_cast
The dynamic_cast operator performs type conversions at run time.
It guarantees the conversion of a pointer to a base class to a pointer to a derived class. A program can thereby use a class hierarchy safely.
The primary purpose of dynamic_cast is to perform downcasts.
Is this legal?class A {public: virtual void foo() {}};
class B: public virtual A { };class C: public virtual A { };class D: public B, public C { };
main() { A *pa = new A; B *pb = new B; C *pc = new C; D *pd = new D;
pb = dynamic_cast<B*> (pa); pc = dynamic_cast<C*> (new B);
pd = dynamic_cast<D*> (pa);}
Yes, this is legal.
Using dynamic_cast on a polymorphic hierarchy, one can convert between pointers to classes up, down or sideways in the hierarchy.
Is this legal?
pb = dynamic_cast<B*> (pa); if (pb) std::cout << "Cast from A to B worked\n"; else std::cout << "Cast from A to B failed\n"; pc = dynamic_cast<C*> (new B); if (pc) std::cout << "Cast from B to C worked\n"; else std::cout << "Cast from B to C failed\n";
pd = dynamic_cast<D*>(pa); if (pd) std::cout << "Cast from A to D worked\n"; else std::cout << "Cast from A to D failed\n";
Yes these are all legal. But the output is:
Cast from A to B failedCast from B to C failedCast from A to D failed
dynamic_cast example
class A {public: virtual void foo() { }};
class B: public virtual A {private: int x;public: B(): x(0) { } B(int a): x(a) { } void bar() { std::cout << x << '\n'; }};
Example part 2
class C: public virtual A {};
class D: public B, public C {};
main()
{
A *pa = new D;
B *pb;
pb = dynamic_cast<B*> (pa);
if (pb) pb->bar();
else std::cout << "Cast failed\n";
The cast pb = dynamic_cast<B*> (pa); works because pa points at a D, which contains a B subobject.
RTTI
There is an operator typeid that can be used to check the types of objects.
The return type of typeid is an object of class type_info.
The == and != operators are overloaded for type_info.
One can use the member function type_info::name() to view the type name.
RTTI Example
class A { };class B: public A { };
main(){ A a1, a2; B *pb;
int x;
RTTI Example Part 2
if (typeid(a1) == typeid(a2)) std::cout << "Variable a1 is of type " << typeid(a1).name() << '\n'; std::cout << "Variable x is of type “ << typeid(x).name() << '\n'; std::cout << "Variable pb is of type " << typeid(pb).name() << '\n';
RTTI Example Part 3
Output from program
Variable a1 is of type 1A
Variable x is of type i
Variable pb is of type P1B
const_cast
Is used to add or remove const or volatile qualifiers.
Example
double f(double &d) { return d*d; }
void g(const double &x)
{
f(const_cast<double>(x));
}