Examples - using lists

Posted by Nick Gammon on Wed 02 Jul 2003 07:09 AM — 3 posts, 20,548 views.

Australia Forum Administrator #0
The example below shows creating a list of people, adding to the tail and the head. We then use for_each to print each one using the Print member function. Then all pointers are deleted.





#include <string>
#include <list>
#include <algorithm>
#include <iostream>
#include <functional>

using namespace std;

struct DeleteObject
  {
  template <typename T>
  void operator() (const T* ptr) const { delete ptr; };
  };

class Person
  {
  // private values
  string m_sName;
  string m_sEmail;
  int    m_iAge;

  public:

  // constructor
  Person (const string sName, 
          const string sEmail, 
          const int iAge) :
  m_sName (sName), m_sEmail (sEmail), m_iAge (iAge) {};

  // this is a bit wacky but the MS compiler would not compile
  // (the for_each) without a return value, however who cares 
  // what it is?

  // print this person
  int Print (void) 
    { 
    cout << m_sName 
         << " - "     << m_sEmail 
         << ", age "  << m_iAge 
         << endl;
    return 0;   // return value gets discarded
    };

  };

// a list of people pointers
typedef list<Person*> PersonList;

int main (void)
  {
  PersonList people;

  // add items to list
  people.push_back  (new Person ("Nick", "nick@some-email-address.com", 15));
  people.push_back  (new Person ("Fred", "fred@nurk.com.au", 100));
  people.push_front (new Person ("John", "john@smith.com.au", 35));

  // print everyone (calls member function Print in the person class)
  for_each (people.begin (), people.end (), mem_fun (&Person::Print));

  // delete all objects in list
  for_each (people.begin (), people.end (), DeleteObject ());
  // now delete them from the list
  people.clear ();

  return 0;
  } // end of main


Output

John - john@smith.com.au, age 35
Nick - nick@some-email-address.com, age 15
Fred - fred@nurk.com.au, age 100


Amended on Wed 24 Sep 2003 02:28 AM by Nick Gammon
Australia Forum Administrator #1
The next example demonstrates various ways of getting information from the data in the list.

To save worrying about pointers and deleting them I have reworked the earlier class to be "straight" (not a list of pointers to people, just a list of people). In order to support this I added a copy and assignment operator.

The various ways of outputting the person's name, email and age are:

  • Via a function built into the class (Person::Print)
  • Via a separate function (PrintPerson)
  • Via a function object (fPrint) which takes an output stream as an argument. Two examples here show outputting directly to cout, or to an output stream (so the output can be a string inside the program).
  • Via an ostream iterator (ostream& operator<<) which lets you stream output from the Person object to an output stream.
  • Using remove_copy_if to selectively output to the ostream iterator - this lets you print a subset of the list.
  • Doing the same thing but to a custom-written iterator - this would let you totally control what was output and where it went.


All of the above may make this example look a bit cluttered, but you only need to use one of the methods in practice, however the example shows there are lots of ways of achieving a similar result.




#include <string>
#include <list>
#include <algorithm>
#include <iostream>
#include <sstream>
#include <iterator>
#include <functional>

using namespace std;

class Person
  {
  // private members
  string m_sName;
  string m_sEmail;
  int    m_iAge;

  public:

  // constructor
  Person (const string sName, 
          const string sEmail, 
          const int iAge) :
   m_sName (sName), m_sEmail (sEmail), m_iAge (iAge) {};

  // default constructor
  Person () : m_iAge (0) {};

  // copy constructor
  Person (const Person & p) :
    m_sName (p.m_sName), m_sEmail (p.m_sEmail), m_iAge (p.m_iAge) {};
    
  // operator =
  Person & operator= (const Person & rhs)
    {
    // don't assign to self
    if (this == &rhs)
      return *this;

    m_sName = rhs.m_sName;
    m_sEmail = rhs.m_sEmail;
    m_iAge = rhs.m_iAge;
    return *this;
    };

  // this is a bit wacky but the MS compiler would not compile
  // (the for_each) without a return value, however who cares 
  // what it is?

  // print this person
  int Print (void) 
    { 
    cout << m_sName 
         << " - "     << m_sEmail 
         << ", age "  << m_iAge 
         << endl;
    return 0;   // return value gets discarded
    };

  // access private members

  string GetName ()   const { return m_sName; };
  string GetEmail ()  const { return m_sEmail; };
  int GetAge ()       const { return m_iAge; };

  }; // end of class Person

// ostream iterator for Person class
ostream& operator<< (ostream& os, const Person & p)
 {
 os << p.GetName () 
     << " - "     << p.GetEmail () 
     << ", age "  << p.GetAge () 
     << endl;
 return os;
 };  // end of ostream& operator<<

// my own iterator can be used for outputting
class myiterator : public iterator <output_iterator_tag, Person>
  {
  public:
    // assignment is used to output the value
    myiterator & operator= (const Person & p)
      { 
      cout << p.GetName () << endl;
      return *this;
      };

    // dereference and increments are no-ops that return 
    // the iterator itself
    myiterator & operator* () { return *this; };
    myiterator & operator++ () { return *this; };
    myiterator & operator++ (int) { return *this; };

  };  // end of class myiterator

// function to print one person
void PrintPerson (const Person & p)
  {
   cout << p.GetName () 
       << " - "     << p.GetEmail () 
       << ", age "  << p.GetAge () 
       << endl;

  } // end of PrintPerson

// function object to print one person
class fPrint 
  {

  ostream & m_os;

  public:

  // constructor - remember which stream to use
  fPrint (ostream & os) : m_os (os) {};

  void operator() (const Person & p)
    {
     m_os << p.GetName () 
         << " - "     << p.GetEmail () 
         << ", age "  << p.GetAge () 
         << endl;
    };

  };  // end of class fPrint

// this function object is used to test a field inside the
// Person class
struct age_less_than : binary_function <Person, int, bool>
  {
  bool operator() (const Person & p, const int iAge) const
    { return p.GetAge () < iAge; };
  };  // end of struct age_less_than

int main (void)
  {
  // make a list of people
  list<Person> people;

  // add items to list
  people.push_back  (Person ("Nick", "nick@some-email-address.com", 15));
  people.push_back  (Person ("Fred", "fred@nurk.com.au", 100));
  people.push_front (Person ("John", "john@smith.com.au", 35));

  // print everyone (calls member function Print in the person class)
  cout << "Using mem_fun_ref ..." << endl;
  for_each (people.begin (), people.end (), mem_fun_ref (&Person::Print));

  // print everyone (calls a separate function to print)
  cout << "Using PrintPerson ..." << endl;
  for_each (people.begin (), people.end (), PrintPerson);

  // print everyone (calls a function object to print)
  cout << "Using fPrint to cout ..." << endl;
  for_each (people.begin (), people.end (), fPrint (cout));

  // for copying results into
  ostringstream os;

  // print everyone to memory
  cout << "Using fPrint to memory ..." << endl;
  for_each (people.begin (), people.end (), fPrint (os));

  cout << "Memory stream contained:" << endl << os.str ();

  // print everyone using an ostream_iterator
  cout << "Using ostream_iterator ..." << endl;
  copy (people.begin (), people.end (), ostream_iterator<Person> (cout));

  // remove_copy_if copies everyone EXCEPT those who meet the condition
  // (thus removing them, in a sense) so we use not1 to reverse that condition

  // use remove_copy_if to show people who meet a condition
  cout << "Show people < 20 years old ..." << endl;
  remove_copy_if (people.begin(), people.end(), 
                  ostream_iterator<Person>(cout), 
                  not1 (bind2nd (age_less_than (), 20)));

  // by using remove_copy_if on its own this time we actually 
  // get people who are NOT less than 50

  // use remove_copy_if to send people who meet a condition to my iterator
  cout << "Show people >= 50 years old ..." << endl;
  remove_copy_if (people.begin(), people.end(), 
                  myiterator (),  // output iterator takes result
                  bind2nd (age_less_than (), 50));

  return 0;
  } // end of main



Output


Using mem_fun_ref ...
John - john@smith.com.au, age 35
Nick - nick@some-email-address.com, age 15
Fred - fred@nurk.com.au, age 100
Using PrintPerson ...
John - john@smith.com.au, age 35
Nick - nick@some-email-address.com, age 15
Fred - fred@nurk.com.au, age 100
Using fPrint to cout ...
John - john@smith.com.au, age 35
Nick - nick@some-email-address.com, age 15
Fred - fred@nurk.com.au, age 100
Using fPrint to memory ...
Memory stream contained:
John - john@smith.com.au, age 35
Nick - nick@some-email-address.com, age 15
Fred - fred@nurk.com.au, age 100
Using ostream_iterator ...
John - john@smith.com.au, age 35
Nick - nick@some-email-address.com, age 15
Fred - fred@nurk.com.au, age 100
Show people < 20 years old ...
Nick - nick@some-email-address.com, age 15
Show people >= 50 years old ...
Fred

Amended on Fri 09 Jul 2004 01:04 AM by Nick Gammon
Australia Forum Administrator #2
Finally I want to illustrate using composing function objects to do the same general thing (find people in an age range) without having to write a special function object to do the compare.

I had trouble doing this in the past, which I now think is due to a) inexperience; and b) the fact the Visual C++ 6 does not correctly compile some templated functions.

The code below compiles under gcc (GCC) 3.2.2, and may well compile under Visual C++ 7, however I haven't tested that. Under Visual C++ 6 I get an error C2664 when compiling it.

What this example does is combine various things, the important lines are in bold.

The functions are:

  • not1 - to reverse the sense of the copy, as remove_copy_if copies everything except the condition specified, so by using not1 we copy everything matching the condition
  • compose_f_gx - this combines two functions (see the Josuttis book for details), effectively giving the results of:


    f(g(element))


    To quote briefly from the book: "the result of calling predicate g () for element is used as input for predicate f ()".

    What this is going to do is first get the person's age (for each element), and then apply the "less" function to each one, with the hard-coded number 40 as what we are comparing it to.

    This is in the "boost" namespace, hence the "using namespace boost" near the start of the file.
  • bind2nd - this is combining "less" and "40" to apply the test "less than 40" to each element
  • less<int> - this is generating the function "less than" for an integer
  • mem_fun_ref - this generates a function call for the argument "by reference", namely "GetAge" for a "Person".


Complicated, huh? No wonder it took a while to get right.

If you want the "compose.hpp" file is is available from the Josuttis site, but to save you the trouble of finding it a copy is available here:

http://gammon.com.au/files/utils/compose.hpp




Finally, the example program uses "transform" to copy the ages only from the first list into another one. Again this uses mem_fun_ref to extract the ages only.



#include <string>
#include <list>
#include <iostream>
#include <iterator>
#include <functional>

// extra include for compose_f_gx function
#include "compose.hpp"

using namespace std;
using namespace boost;

class Person
  {
  // by making the operator<< a friend we can print directly
  // from the member variables - since it is passed a const
  // copy of the Person, it cannot change them
  friend ostream& operator<< (ostream& os, const Person & p);

  // private members
  string m_sName;
  string m_sEmail;
  int    m_iAge;

  public:

  // constructor
  Person (const string sName,
          const string sEmail,
          const int iAge) :
   m_sName (sName), m_sEmail (sEmail), m_iAge (iAge) {};

  // default constructor
  Person () : m_iAge (0) {};

  // copy constructor
  Person (const Person & p) :
    m_sName (p.m_sName), m_sEmail (p.m_sEmail), m_iAge (p.m_iAge) {};

  // operator =
  Person & operator= (const Person & rhs)
    {
    // don't assign to self
    if (this == &rhs)
      return *this;

    m_sName = rhs.m_sName;
    m_sEmail = rhs.m_sEmail;
    m_iAge = rhs.m_iAge;
    return *this;
    };

  // access private members

  string GetName ()   const { return m_sName; };
  string GetEmail ()  const { return m_sEmail; };
  int GetAge ()       const { return m_iAge; };

  }; // end of class Person

// ostream operator<< for Person class
ostream& operator<< (ostream& os, const Person & p)
 {
 os << p.m_sName << " - "     << p.m_sEmail
    << ", age " << p.m_iAge
    << endl;
 return os;
 };  // end of ostream& operator<<


int main (void)
  {
  // make a list of people
  list<Person> people;

  // add items to list
  people.push_back  (Person ("Nick", "nick@some-email-address.com", 15));
  people.push_back  (Person ("Fred", "fred@nurk.com.au", 100));
  people.push_front (Person ("John", "john@smith.com.au", 35));

  // print everyone using an ostream_iterator
  cout << "Using ostream_iterator ..." << endl;
  copy (people.begin (), people.end (), ostream_iterator<Person> (cout));

  // remove_copy_if copies everyone EXCEPT those who meet the condition
  // (thus removing them, in a sense) so we use not1 to reverse that condition

  // use remove_copy_if to show people who meet a condition
  cout << "Show people < 40 years old ..." << endl;
  remove_copy_if (people.begin(), people.end(),
                  ostream_iterator<Person>(cout),
                  not1 (compose_f_gx (
                          bind2nd (less<int> (), 40),
                          mem_fun_ref (&Person::GetAge))
                        ));

  // extract ages using member function

  list<int> ages;
  transform (people.begin(), people.end(), back_inserter (ages),
             mem_fun_ref (&Person::GetAge));

  copy (ages.begin (), ages.end (), ostream_iterator<int> (cout, " "));

  return 0;
  } // end of main



Output


Using ostream_iterator ...
John - john@smith.com.au, age 35
Nick - nick@some-email-address.com, age 15
Fred - fred@nurk.com.au, age 100
Show people < 40 years old ...
John - john@smith.com.au, age 35
Nick - nick@some-email-address.com, age 15
35 15 100