Use every possible means to release the resources you consume, especially memory. Valgrind can be a nice assistant to track memory leaks (voir Valgrind). 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 ofstd::auto_ptr
starting with theTranslate
module, and finally use of reference counting via smart pointers for the intermediate representation.
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. You should strive to prevent code duplication to sneak into your code. Every C++ feature is good to prevent code duplication: inheritance, templates etc.
dynamic_cast
of referencesOf 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 astd::bad_cast
: they are equally safe.
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 comparable_to (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::comparable_to (const Type &rhs) { return &rhs == this || dynamic_cast <Nil *> (&rhs); } bool Nil::comparable_to (const Type &rhs) { return &rhs == this || dynamic_cast <Record *> (&rhs); } bool comparable_to (const Type &lhs, const Type &rhs) { return lhs->comparable_to (rhs); }
dynamic_cast
for type casesDid 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
anddynamic_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 needtypeid
, so usedynamic_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 atypeid
are not the same. For instance, think of a classA
with subclassB
with subclassC
; 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 withoutvtable
, or even builtin types (int
etc.).dynamic_cast
requires a dynamic hierarchy. Note that the ability oftypeid
on static hierarchies can be a pitfall; 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 isA
(!). Usingdynamic_cast
here will simply not compile1. Note that if you provideA
with a virtual methods table (e.g., uncomment the destructor), then thetypeid
of *a isB
.- Compromising the future for the sake of speed
- Because the job performed by
dynamic_cast
is more complex, it is also significantly slower thattypeid
, 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 basedtypeid
will probably not.More material can be found the chapter 9 of voir Thinking in C++ Volume 2: Run-time type identification.
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.
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 &b, T &b) { T c = a; a = b; b = c; }
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. Note that
new
creates an object, returns it together with the responsibility to calldelete
: 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); }
LikeThis
Class should be named in mixed case; for instance
Exp
,StringExp
,TempMap
,InterferenceGraph
etc. This applies to class templates. Voir CStupidClassName.
like_this
No upper case letters, and words are separated by an underscore.
like_this_
It is extremely convenient to have a special convention for private and protected members: you make it clear to the reader, you avoid gratuitous warnings about conflicts in constructors, you leave the “beautiful” name available for public members etc. We used to write
_like_this
, but such words are likely to be used by your compiler or standard library2.For instance, write:
class IntPair { public: IntPair (int first, int second) : first_ (first), second_ (second) { } protected: int first_, second_; }Voir CStupidClassName.
typedef
foo_type
We declaring a
typedef
, name the type foo_type
(where foo is obviously the part that changes). For instance:typedef std::map< const Symbol, Entry_T > map_type; typedef std::list< map_type > symtab_type;We used to use foo
_t
, unfortunately this pseudo name space is reserved by posix.
super_type
It is often handy to define the type of “the” super class (when there is a single one); use the name
super_type
in that case. For instance most Visitors of the ast start with:class TypeVisitor : public ast::DefaultVisitor<ast::non_const_kind> { typedef ast::DefaultVisitor<ast::non_const_kind> super_type; using super_type::visit; // ...