4

I am working on Michael J Laszlo's Book 'Computation Geometry and Computer Graphics in C++' . The following is a template class prototype:

template <class T> class ListNode : public Node {    
    T _val;
    ListNode (T val);
    friend class List<T>;
};

template <class T> ListNode <T>::ListNode(T val)
{_val=val;};

template <class T> class List{    
  private:
    ListNode <T> *header;
    ListNode <T> *win;
    int _length;

  public:
    List(void);
    ~List(void);
    T insert(T);
    T append(T);
    List * append(List*);
    T prepend(T);
    T remove(void);
    void val(T); // overloaded function!
    T val(void);// overloaded function!
    T next(void);
    T prev(void);
    T first(void);
    T last(void);
    int length(void);
    bool isFirst(void);
    bool isLast(void);
    bool isHead(void);
};    

Now look at the way he defines the List constructor:

//  constructors and destructors

template <class T> list<T>:: List(void): _length(0)
{    
    header =new ListNode<T>(NULL);
    win=header;
}

My Question:

What is up with the assigning a default length outside the {...} and the rest inside? Is there some sort of logical reasoning behind this?

Because for example before this, he pretty much declared everything outside the {...} and I assumed that was just his style

CharlesB
  • 86,532
  • 28
  • 194
  • 218
metric-space
  • 541
  • 4
  • 14
  • Do you wonder why he didn't continue after `_length(0)` with `header(new(ListNode....)), win(header) {}`? – Damon May 24 '13 at 15:43
  • Yes, I assumed that the question implied that, but yes I do wonder as to why he didn't do so. – metric-space May 24 '13 at 15:44
  • 2
    It's a pretty standard practice. Take a look at [this question](http://stackoverflow.com/questions/926752/why-should-i-prefer-to-use-member-initialization-list) – Nik Bougalis May 24 '13 at 15:44
  • 2
    @nerorevenge: Well, he could as well have done that, and it would likely be a little more efficient. But then, what do you do when `operator new` throws? Not like there's a `try/catch` in this constructor, but there _could be_. On the other hand, since `header` is the only allocated resource, there's nothing that could possibly be leaked (if `new` fails, it fails... so there's no `header`). So, there's really no good reason against it in this case. – Damon May 24 '13 at 15:46
  • 1
    Consider this: http://www.parashift.com/c++-faq-lite/init-lists.html – Jack May 24 '13 at 15:47
  • @Damon could you expand on the `try/catch` point you make? – metric-space May 24 '13 at 15:49
  • @Damon, Though you can use a function try-catch block, but I prefer just doing that in the body in that case. – chris May 24 '13 at 15:51
  • @nerorevenge: Well, `operator new` will throw `bad_alloc` if it can't fulfill the request. If you encounter exceptions, you need to be careful so the objects that you've already created before are properly deleted. Let's say you had `_length(new int)`, then there'd be no way to delete that in case `new ListNode` throws. @chris: Funny idea, didn't think of that. But yes, you could write `header(func())` and have something like `try{return new ListNode; }catch(bad_alloc){return 0;}` in `func`. Or, of course... `std::nothrow`. – Damon May 24 '13 at 15:56

4 Answers4

17

What is up with the assigning a default length outside the parenthesis and the rest inside the curly braces?

This is very common and desirable. The construct is called initialization list. For example, this

template <class T> ListNode <T>::ListNode(T val)
{_val=val;};

can be rewritten as this:

template <class T> ListNode <T>::ListNode(T val)
: _val(val) {};

Using this construct instructs the compiler to use copy constructors for items being copied, rather than using default constructors followed by assignment operators. In case of assigning primitives it hardly matters, but with more complex types initialization lists could save you some CPU cycles.

The reason the author did not put the header and win assignments into the initialization list is to force a particular order of initialization where it matters. The assignment to header must happen before the assignment to win. When you use initialization lists, the order of assignments is not controlled by the order of items in the list: instead, it is controlled by the relative order of declaration of the corresponding members in the class. Relying upon it is very confusing to the reader and too fragile to remain in production, so the author correctly decided to move the two assignments into the body of the constructor.

Community
  • 1
  • 1
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    well if it's for efficiency part, how come he hasn't bothered putting the rest of what's inside the parenthesis within the initialization list? – metric-space May 24 '13 at 15:51
  • 1
    @nerorevenge That's an excellent question! Take a look at the updated answer. – Sergey Kalinichenko May 24 '13 at 15:54
  • wow the 'the relative order of declaration' is something I never bothered to take into consideration. – metric-space May 24 '13 at 16:05
  • 1
    I strongly disagree that relying on declaration order is "too fragile to remain in production". I happily rely on it, and build production code with `-Werror=reorder` so noone can screw it up. – Jonathan Wakely May 24 '13 at 16:21
  • @JonathanWakely Although this practice is OK in small groups of developers, it quickly becomes a liability when the number of developers in your group approaches a few dozen, or when porting your code to other platforms is offloaded to another group. I got this "better be safe than sorry" mentality after working in a company with six server platforms, where the porting to other platforms was done externally to our team. Some platforms may not have the `-Werror=reorder` switch, so the porting team might decide to exclude it. – Sergey Kalinichenko May 24 '13 at 16:33
  • So shoot people who break the code ;) Why are porters reordering member variables anyway? I prefer to use init-lists, rely on the defined semantics of the language, and if necessary put a comment saying `// do not reorder these!`. You only need the `-Werror=reorder` flag on **one** platform's makefile and you'll notice the build is broken. – Jonathan Wakely May 24 '13 at 16:37
  • @JonathanWakely In our team there was a $5 required donation that replaced shooting for people who break the code. We ate pizza for the first two months, and then the stream of donations quietly dried up. Code porting team was in a different city, so we couldn't even go beat them up. In addition, it was back in 1999-2001, so not all our C++ compilers were up to the "new" C++98 standard. Perhaps these days it is much less of an issue, but we were very cautious back in the day about using even the fundamental features of the language. – Sergey Kalinichenko May 24 '13 at 16:43
  • 1
    Heh, that's a better solution than shooting people, you get pizza and don't have to keep hiring new people :) – Jonathan Wakely May 25 '13 at 10:11
6

The way I'm reading the question, you're not asking why _length is initialised in the initialiser list, but you're asking why header and win aren't.

Suppose you had

template <class T> List<T>::List(void)
    : _length(0)
    , header(new ListNode<T>(NULL))
    , win(header)
{ }

What would this do? Would this initialise header and then copy it to win, or would this first copy header to win and only then set header? You cannot tell by looking at the constructor definition. You can tell by looking at the constructor definition when plain old assignments are used. So some (myself included) might say the code is easier to read the way it is.

  • 1
    Note that modern compilers will warn you if an initialization list doesn't match the true order of initialization. – Drew Dormann May 24 '13 at 15:58
  • The way I intended the question to be read out is this way: if there is an obvious reason to include it in initializer list, why not do it for every thing ,and if no why bother not sticking it in within the parenthesis , and if there is something with the variable that it had to be initialised that way? – metric-space May 24 '13 at 15:58
  • @nerorevenge The initialization list is generally *preferred*. It prevents double-initialization and it allows initialization of `const` members. In the example you post, all variables could be initialized that way. – Drew Dormann May 24 '13 at 16:01
  • @nerorevenge Ah, in that case, it depends. Very often the initialisers can be run in any order, and if the order doesn't matter, it also doesn't matter that it's hard to tell what the order is. In that case, as mentioned, initialisation is usually a better choice. Since the order does matter here, an exception to the general rule can be made (but as noted, it doesn't, strictly speaking, need to be). –  May 24 '13 at 16:04
  • @hvd what do you mean by 'it doesn't need ,strictly speaking, need to be?' – metric-space May 24 '13 at 16:07
  • 1
    @nerorevenge I mean the order of the fields in the class declaration happens to match the order in which they need to be initialised, so you *could* get away with not using assignments. But it means that changes to the class layout become quite tricky to manage, as changing the order of the fields breaks the constructor. That's what Drew Dormann pointed out with "In the example you post, all variables could be initialized that way." –  May 24 '13 at 16:14
2

It is for efficiency reason. That region -- before the {} and after the () is called initializer list. You can initialize your variable there. The compiler instead of default initializing your member variables will initialize the variables set in that initializer list. Compare it to the scenerio where you initialize your variable inside the {}. The compiler first initializes all your member variable then goes into the body, {}, then you re-initialize your member variables. The initializer skips that initialization step. Always initialize in the initializer-list when possible.

dchhetri
  • 6,926
  • 4
  • 43
  • 56
2

Firstly, it is more efficient to define values in the initialiser list. If you don't do this, unless you have a clever compiler, the values will be default initialised, and the assigned to. For some types, this is not terribly efficient (although not in the class you have there).

As to why he decided to do it this way for this one class, it's unclear. I can only assume it is so that he can perhaps catch the possible throw from the new in an out of memory situation - although even there he could do a new(nothrow) for that which would probably be both more efficient and clearer.

What he could be trying to do is not to fall foul off the order of initialisation requirements of C++ - class members are initialised in the order they are declared in the class definition, not in the order you specify them in the constructor initialisation list. That can be a real pain, though most compilers now warn you if you are doing that (and also allow you to change that warning into an error). In this case, it would be possible for win to have been declared before header, in which case doing win(header) in the initialiser list would set win to have the value of header, before header got initialised. Although, even there, I'd still have initialised header in the initialiser list, and only put the initialisation of win in the code block.

What really irks me is his use of (void) parameter lists. That's a C-ism, and looks ugly. Especially for default constructors and destructors.

Tom Tanner
  • 9,244
  • 3
  • 33
  • 61