Noeud « Next »: , Noeud « Previous »: Use of Foreign Features, Noeud « Up »: Coding Style



2.3.3 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 (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 of std::auto_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. You should strive to prevent code duplication to sneak 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 aborts, 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
          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);
          }
     
— 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. Note that the ability of typeid 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 is A(!). Using dynamic_cast here will simply not compile1. Note that if you provide A with a virtual methods 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 9 of voir 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 &b, 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. Note that 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: Name your classes LikeThis

Class should be named in mixed case; for instance Exp, StringExp, TempMap, InterferenceGraph etc. This applies to class templates. Voir CStupidClassName.

— Rule: Name public members like_this

No upper case letters, and words are separated by an underscore.

— Rule: Name private/protected members 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.

— Rule: Name your 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.

— Rule: Name the parent class 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;
          // ...
     


Notes de bas de page

[1] For instance, g++ reports an error: cannot dynamic_cast `a' (of type `struct A*') to type `struct B*' (source type is not polymorphic).

[2] Actually, it is _[A-Z] which is reserved.