Fun with C++ class instance member initialization.


R. Clayton (rclayton@monmouth.edu)
Thu, 15 Jun 2000 14:15:34 -0400 (EDT)


Why doesn't this code compile?

  $ cat t0.cc
  #include <vector>
  #include <iostream>

  class readmbox {

    public:

      vector<vector<char> > table(5, vector<char>(80));
    };

  int main(void) {

    readmbox m;

    cout << "m.table.size() = " << m.table.size() << ".\n";

    for (unsigned i = 0; i < m.table.size(); i++)
      cout << "m.table[" <<i<< "].size() = " << m.table[i].size() << ".\n";

    return 0;
    }

  $ g++ -ansi -pedantic -Wall t0.cc
  t0.cc:8: warning: ANSI C++ forbids initialization of member `table'
  [ other error messages deleted ]

  $

The intention seems clear enough: define table to be a vector of five values,
where each value is a vector of 80 character values. So why doesn't it
compile?

The code doesn't compile because it's confusing a major conceptual distinction
made by C++: the difference between a class and an instance of a class. The
problem is, the variable table does not exist at the time the class readmbox is
being defined.

Remember, table is a member variable for an instance of class readmbox, it is
not a member variable of class readmbox itself. When you're defining a class,
you are not creating an instance of that class, and the instance member
variables don't exist. Because the instance member variables don't exist, you
can't initialize them, and your c++ compiler should complain when you try.

The problem occurs because the code is trying to initialize the non-existent
instance member variable table. What's the solution? The solution seems
pretty clear: wait until table exists, then initialize it. One way to do this
is in a constructor for readmbox:

  $ cat t1.cc
  #include <vector>
  #include <iostream>

  class readmbox {

    public:

      readmbox() {
        for (int i = 0; i < 5; i++)
          table.push_back(vector<char>(80));
        }

      vector<vector<char> > table;
    };

  int main(void) {

    readmbox m;

    cout << "m.table.size() = " << m.table.size() << ".\n";

    for (unsigned i = 0; i < m.table.size(); i++)
      cout << "m.table[" <<i<< "].size() = " << m.table[i].size() << ".\n";

    return 0;
    }

  $ g++ -o t1 -ansi -pedantic -Wall t1.cc

  $ ./t1
  m.table.size() = 5.
  m.table[0].size() = 80.
  m.table[1].size() = 80.
  m.table[2].size() = 80.
  m.table[3].size() = 80.
  m.table[4].size() = 80.

  $

This code works because the constructor creates an instance of class readmbox;
because the instance exists, so does the instance member variable table, and it
can be initialized.

Looking at t1.cc, what readmbox() is essentially doing is implementing
number-and-value initialization for vectors. This seems a clumsy and
inefficient approach to take, particularly because number-and-value
initialization has already been provided as part of the vector class. Is there
some way to avoid duplicated work by initializing table with the already
defined number-and-class initializer for vectors?

It turns out there is, using the colon initializers in constructors. I won't
go into details, but just present an example:

  $ cat t4.cc
  #include <vector>
  #include <iostream>

  class readmbox {

    public:

      readmbox() : table(5, vector<char>(80)) {
        }

      vector<vector<char> > table;
    };

  int main(void) {

    readmbox m1;

    cout << "m1.table.size() = " << m1.table.size() << ".\n";

    for (unsigned i = 0; i < m1.table.size(); i++)
      cout << "m1.table["<<i<<"].size() = " << m1.table[i].size() << ".\n";

    return 0;
    }

  $ g++ -o t4 -ansi -pedantic -Wall t4.cc

  $ ./t4
  m1.table.size() = 5.
  m1.table[0].size() = 80.
  m1.table[1].size() = 80.
  m1.table[2].size() = 80.
  m1.table[3].size() = 80.
  m1.table[4].size() = 80.

  $

Now the readmbox() constructor does no work itself; all the work takes place in
the vector's number-and-value constructor.



This archive was generated by hypermail 2.0b3 on Fri Aug 11 2000 - 15:25:05 EDT