Use of C++ Features

Rule: Hunt Leaks

Use every possible means to release the resources you consume, especially memory. Valgrind can be a nice assistant to track memory leaks (see Valgrind, The Ultimate Memory Debugger). To demonstrate different memory management styles, you are invited to use different features in the course of your development: proper use of destructors for the AST, use of a factory for symbol, Temp etc., use of std::unique_ptr starting with the Translate module, and finally use of reference counting via smart pointers for the intermediate representation.

Rule: Hunt code duplication

Code duplication is your enemy: the code is less exercised (if there are two routines instead of one, then the code is run half of the time only), and whenever an update is required, you are likely to forget to update all the other places. Strive to prevent code duplication from sneaking into your code. Every C++ feature is good to prevent code duplication: inheritance, templates etc.

Rule: Prefer dynamic_cast of references

Of the following two snippets, the first is preferred:

const IntExp& ie = dynamic_cast<const IntExp&>(exp);
int val = ie.value_get();
const IntExp* iep = dynamic_cast<const IntExp*>(&exp);
assert(iep);
int val = iep->value_get();

While upon type mismatch the second abort s, the first throws a std::bad_cast: they are equally safe.

Rule: Use virtual methods, not type cases

Do not use type cases: if you want to dispatch by hand to different routines depending upon the actual class of objects, you probably have missed some use of virtual functions. For instance, instead of

bool
compatible_with(const Type& lhs, const Type& rhs)
{
  if (&lhs == &rhs)
    return true;
  if (dynamic_cast<Record*>(&lhs))
    if (dynamic_cast<Nil*>(&rhs))
       return true;
  if (dynamic_cast<Record*>(&rhs))
    if (dynamic_cast<Nil*>(&lhs))
       return true;
  return false;
}

write

bool
Record::compatible_with(const Type& rhs)
{
  return &rhs == this || dynamic_cast<const Nil*>(&rhs);
}

bool
Nil::compatible_with(const Type& rhs)
{
  return dynamic_cast<const Record*>(&rhs);
}
Rule: Use dynamic_cast for type cases

Did you read the previous item, “Use virtual methods, not type cases”? If not, do it now.

If you really need to write type dispatching, carefully chose between typeid and dynamic_cast. In the case of tc, where we sometimes need to down cast an object or to check its membership to a specific subclass, we don’t need typeid, so use dynamic_cast only.

They address different needs:
dynamic_cast for (sub-)membership, typeid for exact type

The semantics of testing a dynamic_cast vs. a comparison of a typeid are not the same. For instance, think of a class A with subclass B with subclass C; then compare the meaning of the following two snippets:

// Is `a' containing an object of exactly the type B?
bool test1 = typeid(a) == typeid(B);
// Is `a' containing an object of type B, or a subclass of B?
bool test2 = dynamic_cast<B*>(&a);
Non polymorphic entities

typeid works on hierarchies without vtable, or even builtin types (int etc.). dynamic_cast requires a dynamic hierarchy. Beware of typeid on static hierarchies; for instance consider the following code, courtesy from Alexandre Duret-Lutz:

#include <iostream>

struct A
{
  // virtual ~A() {};
};

struct B: A
{
};

int
main()
{
  A* a = new B;
  std::cout << typeid(*a).name() << std::endl;
}

it will “answer” that the typeid of *a is A (!). Using dynamic_cast here will simply not compile [1].If you provide A with a virtual function table (e.g., uncomment the destructor), then the typeid of *a is B.

Compromising the future for the sake of speed

Because the job performed by dynamic_cast is more complex, it is also significantly slower that typeid, but hey! better slow and safe than fast and furious.

You might consider that today, a strict equality test of the object’s class is enough and faster, but can you guarantee there will never be new subclasses in the future? If there will be, code based dynamic_cast will probably behave as expected, while code based typeid will probably not.

More material can be found the chapter 8 of see Thinking in C++ Volume 2: Run-time type identification.

Rule: Use const references in arguments to save copies (EC22)

We use const references in arguments (and return value) where otherwise a passing by value would have been adequate, but expensive because of the copy. As a typical example, accessors ought to return members by const reference:

const Exp&
OpExp::lhs_get() const
{
  return lhs_;
}

Small entities can be passed/returned by value.

Rule: Use references for aliasing

When you need to have several names for a single entity (this is the definition of aliasing), use references to create aliases. Note that passing an argument to a function for side effects is a form of aliasing. For instance:

template <typename T>
void
swap(T& a, T& b)
{
  T c = a;
  a = b;
  b = c;
}
Rule: Use pointers when passing an object together with its management

When an object is created, or when an object is given (i.e., when its owner leaves the management of the object’s memory to another entity), use pointers. This is consistent with C++: new creates an object, returns it together with the responsibility to call delete: it uses pointers. For instance, note the three pointers below, one for the return value, and two for the arguments:

OpExp*
opexp_builder(OpExp::Oper oper, Exp* lhs, Exp* rhs)
{
  return new OpExp(oper, lhs, rhs);
}
Rule: Avoid static class members (EC47)

More generally, “Ensure that non-local static objects are initialized before they’re used”, as reads the title of EC47.

Non local static objects (such as std::cout etc.) are initialized by the C++ system even before main is called. Unfortunately there is no guarantee on the order of their initialization, so if you happen to have a static object which initialization depends on that of another object, expect the worst. Fortunately this limitation is easy to circumvent: just use a simple Singleton implementation, that relies on a local static variable.

This is covered extensively in EC47.

Rule: Use foo_get, not get_foo

Accessors have standardized names: foo_get and foo_set.

There is an alternative attractive standard, which we don’t follow:

class Class
{
public:
  int foo();
  void foo(int foo);
private:
  int foo_;
}

or even

class Class
{
public:
  int foo();
  Class& foo(int foo);  // Return *this.
private:
  int foo_;
}

which enables idioms such as:

{
  Class obj;
  obj.foo(12)
     .bar(34)
     .baz(56)
     .qux(78)
     .quux(90);
}
Rule: Use dump as a member function returning a stream

You should always have a means to print a class instance, at least to ease debugging. Use the regular operator<< for standalone printing functions, but dump as a member function. Use this kind of prototype:

std::ostream& Tree::dump(std::ostream& ostr [, ...]) const

where the ellipsis denote optional additional arguments. dump returns the stream.

Footnotes