2. Language Pragmatics
A language is not just syntax and learning
a language isn’t just learning to program.
Mastering a language requires good
understanding of language semantics,
pragmatics, traps and pitfalls with
considerable experience in programming
and design using that language.
3. Five Specific OO Tips and Techniques
We’ll see 5 specific tips/techniques
Based on understanding, experience and
usage of language features
Tips are about pragmatics of using
language features
The mistakes covered in the tips are errors
in usage
Examples in C++ and Java (sometimes in
C#)
4. 1. Avoid calling virtual functions in constructors
Constructors do not support runtime
polymorphism fully as the derived objects are
not constructed yet when base class constructor
executes.
So, avoid calling virtual functions from base-
class constructors, which might result in subtle
bugs in the code.
5. C++ resolves virtual function calls to base type
struct base {
base() {
vfun();
}
virtual void vfun() {
cout << “Inside base::vfunn”;
}
};
struct deri : base {
virtual void vfun() {
cout << “Inside deri::vfunn”;
}
};
int main(){
deri d;
}
6. C++ resolves virtual function calls to base type
struct base {
base() {
vfun();
}
virtual void vfun() {
cout << “Inside base::vfunn”;
}
};
struct deri : base {
virtual void vfun() {
cout << “Inside deri::vfunn”;
}
};
int main(){
deri d;
}
// prints:Inside base::vfun
7. Java/C# resolves virtual function calls dynamically
// Java example
class base {
public base() {
vfun();
}
public void vfun() {
System.out.println(quot;Inside base::vfunquot;);
}
}
class deri extends base {
public void vfun() {
System.out.println(quot;Inside deri::vfunquot;);
}
public static void main(String []s) {
deri d = new deri();
}
}
8. Java/C# resolves virtual function calls dynamically
// Java example
class base {
public base() {
vfun();
}
public void vfun() {
System.out.println(quot;Inside base::vfunquot;);
}
}
class deri extends base {
public void vfun() {
System.out.println(quot;Inside deri::vfunquot;);
}
public static void main(String []s) {
deri d = new deri();
}
}
// prints: Inside deri::vfun
9. In C++, pure virtual methods might get called
struct base {
base() {
base * bptr = this;
bptr->bar();
// even simpler ...
((base*)(this))->bar();
}
virtual void bar() =0;
};
struct deri: base {
void bar(){ }
};
int main() {
deri d;
}
10. In C++, pure virtual methods might get called
struct base {
base() {
base * bptr = this;
bptr->bar();
// even simpler ...
((base*)(this))->bar();
}
virtual void bar() =0;
};
struct deri: base {
void bar(){ }
};
int main() {
deri d;
}
// g++ output:
// pure virtual method called
// ABORT instruction (core dumped)
11. Dynamic method call in Java might lead to trouble
// Java code
class Base {
public Base() {
foo();
}
public void foo() {
System.out.println(quot;In Base's foo quot;);
}
}
class Derived extends Base {
public Derived() {
i = new Integer(10);
}
public void foo() {
System.out.println(quot;In Derived's foo quot; + i.toString() );
}
private Integer i;
}
class Test {
public static void main(String [] s) {
new Derived().foo();
}
}
12. Dynamic method call in Java might lead to trouble
// Java code
class Base {
public Base() {
foo();
}
public void foo() {
System.out.println(quot;In Base's foo quot;);
}
}
class Derived extends Base {
public Derived() {
i = new Integer(10);
}
public void foo() {
System.out.println(quot;In Derived's foo quot; + i.toString() );
}
private Integer i;
}
class Test {
public static void main(String [] s) {
new Derived().foo();
}
}
// this program fails by throwing a NullPointerException
13. 2. Preserve the basic properties of methods while
overriding
Overriding the methods incorrectly can result in
bugs and unexpected problems in the code.
Adhering to Liskov’s Substitution Principle is
possible only when overriding is done properly.
Make sure that the method signatures match
exactly while overriding is done
Provide semantics similar to the base method in
the overridden method.
14. In C++, provide consistent default parameters
struct Base {
virtual void call(int val = 10)
{ cout << “The default value is :”<< endl; }
};
struct Derived : public Base {
virtual void call(int val = 20)
{ cout << “The default value is :”<< endl; }
};
// user code:
Base *b = new Derived;
b->call();
15. In C++, provide consistent default parameters
struct Base {
virtual void call(int val = 10)
{ cout << “The default value is :”<< endl; }
};
struct Derived : public Base {
virtual void call(int val = 20)
{ cout << “The default value is :”<< endl; }
};
// user code:
Base *b = new Derived;
b->call();
// prints:
// The default value is: 10
16. In Java, final might be removed while overriding
class Base {
public void vfoo(final int arg) {
System.out.println(quot;in Base; arg = quot;+arg);
}
}
class Derived extends Base {
public void vfoo(int arg) {
arg = 0;
System.out.println(quot;in Derived; arg = quot;+arg);
}
public static void main(String []s) {
Base b = new Base();
b.vfoo(10);
b = new Derived();
b.vfoo(10);
}
}
17. In Java, final might be removed while overriding
class Base {
public void vfoo(final int arg) {
System.out.println(quot;in Base; arg = quot;+arg);
}
}
class Derived extends Base {
public void vfoo(int arg) {
arg = 0;
System.out.println(quot;in Derived; arg = quot;+arg);
}
public static void main(String []s) {
Base b = new Base();
b.vfoo(10);
b = new Derived();
b.vfoo(10);
}
}
// prints:
// in Base; arg = 10
// in Derived; arg = 0
18. Provide consistent exception specification
struct Shape {
// can throw any exceptions
virtual void rotate(int angle) = 0;
// other methods
};
struct Circle : public Shape {
virtual void rotate(int angle) throw (CannotRotateException) {
throw CannotRotateException();
}
// other methods
};
// client code
Shape *shapePtr = new Circle();
shapePtr->rotate(10);
// program aborts!
19. 3. Beware of order of initialization problems.
Many subtle problems can happen
because of order of initialization issues.
Avoid code that depends on particular
order of implementation as provided by the
compiler or the implementation.
20. In C++, such init can cause unintuitive results
// translation unit 1
int i = 10;
// translation unit 2
extern int i;
int j = i;
// j is 0 or 10?
// depends on the compiler/link line.
21. In Java, such init can cause unintuitive results
class Init {
static int j = foo();
static int k = 10;
static int foo() {
return k;
}
public static void main(String [] s) {
System.out.println(quot;j = quot; + j);
}
}
22. In Java, such init can cause unintuitive results
class Init {
static int j = foo();
static int k = 10;
static int foo() {
return k;
}
public static void main(String [] s) {
System.out.println(quot;j = quot; + j);
}
}
// prints
// j=0
23. 4. Avoid switch/nested if-else based on types
Programmers from structured
programming background tend to use
extensive use of control structures.
Whenever you find cascading if-else
statements or switch statements checking
for types or attributes of different types to
take actions, consider using inheritance
with virtual method calls.
24. C# code to switch based on types
public enum Phone {
Cell = 0, Mobile, LandLine
}
// method for calculating phone-charges
public static double CalculateCharges(Phone phone, int seconds){
double phoneCharge = 0;
switch(phone){
case Phone.Cell:
// calculate charges for a cell
case Phone.Mobile:
// calculate charges for a mobile
case Phone.LandLine:
// calculate charges for a landline
}
return phoneCharge;
}
25. C# code with if-else using RTTI
abstract class Phone {
// members here
}
class Cell : Phone {
// methods specific to cells
}
// similar implementation for a LandLine
public static double CalculateCharges(Phone phone, int seconds){
double phoneCharge = 0;
if(phone is Cell){
// calculate charges for a cell
}
else if (phone is LandLine){
// calculate charges for a landline
}
return phoneCharge;
}
26. C# code: Correct solution using virtual functions
abstract class Phone {
public abstract double CalculateCharges(int seconds);
// other methods
}
class Cell : Phone {
public override double CalculateCharges(int seconds){
// calculate charges for a cell
}
}
// similar implementation for a LandLine
// Now let us calculate the charges for 30 seconds
Phone ph = new Cell ();
ph.CalculateCharges(30);
27. 5. Avoid hiding of names in different scopes.
Hiding of names in different scopes is
unintuitive to the readers of the code
Using name hiding extensively can affect
the readability of the program.
Its a convenient feature; avoid name
hiding as it can result in subtle defects and
unexpected problems.
28. Hiding of names can happen in different situations
The name in the immediate scope can hide the
one in the outer scope (e.g. function args and
local variables)
A variable in a inner block can hide a name from
outer block (no way to distinguish the two)
Derived class method differs from a base class
virtual method of same name in its return type or
signature - rather it is hidden.
Derived member having same name and
signature as the base-class non-virtual non-final
member; the base member is hidden (e.g. data
members)
29. C++ examples for name hiding
// valid in C++, error in Java/C#
void foo { // outer block
int x, y;
{ // inner block
int x = 10, y = 20;
// hides the outer x and y
}
}
// C++ Code
int x, y; // global variables x and y
struct Point {
int x, y; // class members x and y
Point(int x, int y); // function arguments x and y
};
30. C++/Java/C# example for a bug with hiding
// Bug in C++, Java and C#
Point(int x, int y) {
x = x;
y = y;
}
// C++
Point(int x, int y) {
this->x = x;
this->y = y;
}
// Java and C#
Point(int x, int y) {
this.x = x;
this.y = y;
}
31. C++: No overloading across scopes
struct Base {
void foo(int) {
cout<<quot;Inside Base::foo(int)quot;;
}
};
struct Derived : public Base {
void foo(double) {
cout<<quot;Inside Derived::foo(double)quot;;
}
};
Derived d;
d.foo(10);
32. C++: No overloading across scopes
struct Base {
void foo(int) {
cout<<quot;Inside Base::foo(int)quot;;
}
};
struct Derived : public Base {
void foo(double) {
cout<<quot;Inside Derived::foo(double)quot;;
}
};
Derived d;
d.foo(10);
// prints:
// Inside Derived::foo(double)
33. Java: Overloading across scopes!
class base {
public void foo(int i) {
System.out.println(quot;In Base::foo(int)quot;);
}
}
class deri extends base {
public void foo(double i) {
System.out.println(quot;Inside deri::foo(double)quot;);
}
public static void main(String []s) {
deri d = new deri();
d.foo(10);
}
}
34. Java: Overloading across scopes!
class base {
public void foo(int i) {
System.out.println(quot;In Base::foo(int)quot;);
}
}
class deri extends base {
public void foo(double i) {
System.out.println(quot;Inside deri::foo(double)quot;);
}
public static void main(String []s) {
deri d = new deri();
d.foo(10);
}
}
// prints: Inside Base::foo(int)
35. How to write robust code and avoid defects?
Many of the language rules, semantics and
pragmatics are unintuitive
What can help in detecting bugs early?
Tools (but of limited extent)
Extensive testing
Peer review
Good knowledge and experience
No other approach can create robust code than
passion towards writing excellent code