Next: Use of STL, Previous: Name Conventions, Up: Coding Style [Contents][Index]
Use every possible means to release the resources you consume,
especially memory. Valgrind can be a nice assistant to track memory
leaks (see 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 of std::unique_ptr
starting with the Translate
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. Strive to prevent code duplication from sneaking 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 a
std::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 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); }
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
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 typeThe 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);
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 compile4. If you provide A
with a virtual function table (e.g.,
uncomment the destructor), then the typeid
of ‘*a’ is
B
.
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.
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& a, 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. 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); }
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.
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); }
dump
as a member function returning a streamYou 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.
For instance, g++
reports an ‘error: cannot
dynamic_cast `a' (of type `struct A*') to type `struct B*' (source type
is not polymorphic)’.
Next: Use of STL, Previous: Name Conventions, Up: Coding Style [Contents][Index]