Networked Games -- C++ (Part 1)

  • Jian Huang
  • Networked Games
  • The Notes Page

    Just a quick recap of C++


    C++ was invented in 1979. after that, it underwent three major revisions: in 1985 then in 1990 and in 1994 during the C++ standardization process.

    The 1994 C++ standard was proposed by a joint ANSI and ISO committee. That draft largely reflected the state of C++ at that time.

    Soon after the the first draft, STL was created. STL is considered to be both powerful and elegant, but large.

    By 1998, C++ standard, including STL, has been mostly completed.


    There are really two versions of C++: the oringal by Bjarne Stroustrup's design, and the new standard C++, created by Stroustrup and ANSI/ISO committee Important differences exist between the two, for instance, the new style headers and the namespace statement.

    #inlucde < iostream >
    using namespace std;
    
    int main ()
    {
    
      return 0;
    }
    

    Note: C's stdio.h is C++'s iostream.h. In C++, standard C headers are still included, simply add a "c" prefix and drop the .h, i.e:

    #include < math.h >
    --> #include < cmath >
    
    #inlucde < string.h >
    --> #include < cstring >
    
    

    but, this approach is deprecated. you should try to use the new style headers, whenever possible.


    A namespace is a declarative region, with its purpose to localize the names of identifiers to avoid name collisions. The contents of the new-style headers are placed in the std namespace.

    using namespace std; // puts std into the global namespace
    
    class myclass {
      // private functions and variables
      int a;
      
    public:
      // public functions and variables
      void set_a(int num);
      int get_a();
      
    } object-list; //object-list is optional
    
    void myclass :: set_a (int num)
    {
    
    }
    
    myclass ob1, ob2;
    ob1.set_a(10);
    ob2.set_a(99);
    
    

    Function Overloading

    Note: in C,

    char f1(); /* means nothings is said about the parameters*/
    char f1(void); /*means f1 takes no parameters*/
    

    But in C++, the above two are the same! -- meaning f1 takes no parameters

    In C, function prototypes are recommended but technically optional. In C++, prototypes are required!

    in C, a function with non-void return value actually is not required to return a value (in that case, a garbage value is "returned"). In C++, you are required to return something, unless you declared void return type. Also, in C++ the default-to-int-return assumption has been dropped.

    Unlike in C, C++ defines the bool data type, with can only evalute to two values true and false. (bool, true, false are reserved words).


    In C++, functions are defined by function name, return type and parameter list
    void f1 (int a);
    void f1 (int a, int b);
    

    are different.


    Constructor/Destructor

    class myclass {
      // private functions and variables
      int a;
      
    public:
      // public functions and variables
      myclass();
      ~myclass();
      void set_a(int num);
      int get_a();
      
    } object-list; //object-list is optional
    
    myclass:: myclass()
    {
    
    }
    
    myclass:: ~myclass() //destructor takes no input
    {
    
    }
    
    

    It is common to overload a class's constructor function, for 3 main reasons: 1. to gain flexibiity

    class myclass {
      int x;
    public:
      myclass() {x = 0;}
      myclass(int n) {x = n;}
    };
    

    2. to support arrays, you would need myclass() to declare,

      myclass ob[20];
    or, 
      myclass * myp;
      myp = new myclass [20];
    

    3. to create copy constructor

      - the default is a bitwise copy, usually not desirable to do that
      - needed when an object is used to initialize another in a declaration statement
      - needed when an object is passed as a parameter to a function, and
      - needed when a temporary object is created for use as a return value by a function
    

    The most common form of copy constructor is:

      myclass (const myclass &obj) {
        // body of constructor
      }
    

    Note: copy constructor only applies to initializations (i.e. the above 3 scenarios), BUT NOT to assignments.

       myclass x = y; // copy constructor
       func1(y);      // copy constructor called implicitly
       y = func2();   // copy constructor called implicitly
       
       x = y;         // no call to copy constructor. if no "=" overloading, then bitwise copy
    

    Assigning objects

    One object may be assigned to another provided that both objects are of the same type. By default, a bitwise copy of all the -data- memebers is made, including compound data such as arrays.

    Objects passed to functions as parameters are all passed by value. i.e. a bitwise copy of the argument is made. note, in this case, when the function is called, a constructor is called, and when the function returns, the destructor is called.

    A function can return an object as well. the process is the following: a temporary object is created (calling constructor) and after the object is returned, this object is destroyed (calling destructor).

    This kind of assignment is using the default copy constructor, however. the proper way to do so, is to define a correct copy constructor.


    Friend functions

    A function that have access to the private members of a class, without actually being a member of that class, useful for

      - operator overloading
      - creation of certain types of I/O functions
      - for a function to access the private members of two or more different 
        classes
    
    class myclass {
       int d;
    public:
       myclass (int i);
       friend int isfactor (myclass ob);
    };
    
    int isfactor (myclass ob)
    {
       if (ob.d ...
    }
    
    int main()
    {
       myclass ob1;
       
       if (isfactor(ob1)) ...
    }
    

    Note, unless you have great reasons, avoid using friend functions. The following features are always helpful to remember though.

    1. friends are called just like regular functions.
    2. friends can only access private data members in conjunction with an object
       that's declared within or passed to the friend function
    3. a friend function is not inherited. i.e. when a base class includes a
       friend function, that friend is not a friend of a derived class
    4. a friend func can be friends with more than one class
    5. a function can be a member of one class and a friend of another
    

    Inheritance

    Base class access control comes in three types: public, protected, private

    class derived-class-name: access base-class-name {
    
    
    }
    

    As in:

    class B {
       int i;
       
    public:
       B();
    };
    
    class D: public B {
       int j;
    public: 
       D();
    };
    

    If not access control is specified,

      - it is private by default if the derived is a class
      - it is public by default if the derived is a struct
    

    Note:

    - if you want everything remain the same access categories, 
           do public inheritance
    - if you want everything from base to be private,
           do private inheritance
    - in the derived class, members from the base class become:
    
                            public inheri    protected inheri    private inheri
    public    members       public           protected           private
    protected members       protected        protected           private
    private   members       private          private             private
    

    In the derived class, members from the base class is accessible by the derived class:

                            public inheri    protected inheri    private inheri
    public    members       Yes              Yes                 Yes
    protected members       Yes              Yes                 Yes
    private   members       No               No                  No
    

    class base {
        int x;
    public:
        void setx(int x);
    };
    
    class derived : public base {
        int y;
    public:
    	void sety(int y);
    };
    
    derived ob;
    ob.setx(5);  // fine
    ob.sety(10); // fine
    

    class base {
        int x;
    public:
        void setx(int x);
    };
    
    class derived : private base {
        int y;
    public:
    	void sety(int y);
    };
    
    derived ob;
    ob.setx(5);  // illegal
    ob.sety(10); // fine
    

    With inheritance: the constructor functions are executed in order of derivation (base first, derived second), while the destructor functions are called in the reverse order.

    Passing parameters to the constructors is a bit tricky, a chain of argument passing is established. Everything is first passed to the derived class's constructor using an expanded form of the derived class's construction declaration, parameters are then passed along to the base class

    class base {
        int i;
    public:
        base(int n) {i = n; }
    };
    
    class derived: public base {
        int j;
    public:
        derived(int n, int m): base(m) {
    	   cout<<"constructing derived class\n";
    	   j = n;
    	}
    };
    

    note: the derived class's constructor does not have to use any of those parameters passed in, but it needs to pass them on as needed.


    Mutiple inheritance

    There are two ways of multiple inheritance:
      - indirectly inherit through a base class, i.e.
            A --> B --> C, in a multi-level fashion
      - directly inherit from multiple base classes (our focus here)
    
    class derived-class: access B1, access B2, access B3 ... 
    {
    
    
    };
    
    derived-class:: constructor(arg-list): B1(argl), B2(arg2), B3(arg3) ...
    {
    
    }
    

    Note: constructors of base classes are called in left-to-right order and then the constructor of the derived class, and destructors are executed in the reverse order. With multiple inheritance, there is a possibility that one base class could be inherited multiple times, kind of in a dimond setup. Then, there is an ambiguity in that the base could be included twice in the final derived class.

    In C++, the keyword virtual makes sure that ALWAYS only one copy of the base class in included in the final derived class.

    class base {
    
    };
    
    class D1: virtual public base {
    
    };
    
    class D2: virtual public base {
    
    };
    
    class fD: public D1, public D2 {
    
    };
    

    Virtual functions

    A pointer declared as a pointer to a base class can also be used to point to any class derived from that base. You can only use the pointer to access those members that were inherited from the base. Pointer arithmetic is relative to the data type the pointer is declared. So:

       base * p;
       deri x[2];
       p = x;
       p++;    // will not point to x[1]
    

    Given the above is the case, the base class must have all functions to be called even with the derived class. Otherwise the base pointer is useless. virtual function is used for this purpose, declared within a base and redefined by derived classes. Actually, virtual function is the ONLY good way to get runtime binding. To get it, just precede the function's declaration with the keyword virtual.

    When redefining a virtual function in a derived class, the keyword virtual is not needed. NOTE: to design for inheritance, if you want the correct version of a member function to be called, you need to make that function virtual! Also, it's a very good idea to make the destructor virtual, otherwise delete won't call the right one (i.e. base or derived?).


    class base {
    public:
      int i;
      virtual void func() 
      {
        cout<<"do something\n";
      }
    };
    
    class derived : public base {
    public:
      derived1(int x): base(x) {}
      void func() 
      {
        cout<<"do something different\n";
      }
    };
    
    base * p;
    derived ob1;
    
    p = &ob1;
    p->func();
    
    

    output:

    do something different
    

    Note: If derived does not redefine/override func, still the func in base class is called


    Pure virtual functions

    Pure virtual functions achieve two goals:

    1. the base class is simply for interface purpose
    2. the derived class must override this function (you cannot assume any implementation
         in the base class. thus, avoiding possibility of an overlooked error)
    

    Just do:

    virtual type func-name(parameter-list) = 0;
    
    class area {
       double dim1, dim2;
    public:
       void setarea(double d1, double d2);
       virtual double getarea() = 0;
    };
    
    class rectangele: public area {
    public:
       double getarea()
       {
          return d1*d2;
       }
    };
    
    

    Templates for generic functions

    template < class Ttype > ret-type func-name(parameter-list)
    {
      // body of function
    }
    

    The word "class" here only means to specify a generic type. it can be replaced by "typename", i.e.

    template < typename Ttyep > ret-type func-name (parameter-list);  // is also fine
    

    i.e.

    template < class X > void swapargs (X &a, X &b)
    {
      X temp;
      temp = a; a = b; b = temp;
    }
    
    int main()
    {
      int i, j;
      float x, y;
      
      swapargs(i, j); // this works
      swapargs(x, y); // this also work
      swapargs(i, y); // this does not work
      ...
    }
    

    It is the compiler's job to create two versions of swapargs, one for int and one for float, at compile time. The following is also fine:

    template  
    void swapargs (X &a, X &b)
    {
      X temp;
      temp = a; a = b; b = temp;
    }
    

    Note: no other statements can occur between the template statement and the start of the generic function. You can define more than one type in a template statement, just use a comma-separated list:

    template 
    void out1(X x, Y y)
    {
      cout << x << " " << y << end;
    }
    
    out1(10, "hi");
    out2(0.23, 10L);
    

    you can overload a generic function. note, you really overload just one instance of the generic function each time, and the overloaded version will take precedance

    void swapargs(int a, int b)
    {
       cout<<"just to be different\n";
    }
    
    float x, y;
    int i, j;
    swapargs(i, j); // outputs "just to be different"
    swapargs(x, y); // swaps x, y
    

    Generic classes, very useful when class contains generalizable logic, i.e. maintaining a queue of int, float, etc.

    template < class Ttype > class class-name {
    
    
    
    };
    
    class-name < type > ob; // defines a specific instance ob
    

    Member functions of a generic class are themselves automatically generic. No need to define each one explicitly using keyword template. C++ provides a library built upon temalte classes, referred to as STL.

    tempalte < class dT > class list {
       dT data;
       list *next;
    public:
       list(dT d);
       void add(list *node) {node->next = this; next = 0; }
       list *getnext() { return next; }
       dT    getdata() { return data; }
    };
    
    template < class dT > list< dT > :: list(dT d)
    {
       data = d;
       next = 0;
    }
    
    int main()
    {
    
       list < char > L1('a');
       list < char > *p, * last;
       
       last = &L1;
       for (int i = 0; i < 26; i ++) {
          p = new list < char > ('a'+i);
    	  p -> add(last);
          last = p;
       }
    }
    

    In a way, you can treat list< char > as the name of the class we are referring to in this example.

    list < int > int_headnode(1); // this creates the head node of the linked list
    

    or,

    struct addr {
      char name[40];
      char street[40];
      char city[40];
      char state[3];
      char zip[12];
    }
    
    addr structvar;
    
    list address_head(structvar); // this is also fine
    

    Like generic functions, generic class can also have more than one generic data type. simply declare all the data types required in a comma-separated list withint the template specificication

    template  class myclass
    {
      T1 i;
      T2 j;
    public:
      myclass(T1 a, T2 b) { i = a; j = b; }
    }
    
    myclass  ob1(10, 0.23);
    

    A few major differences

    For the most part, C++ is a superset of ANSI C, however a few differences do exist:

    1. in C a character is automatically elevated to int, not in C++
    2. in C declaring a global variable several times is okay (a bad form though), not in C++
    3. in C an identifier has at least 31 significant characters. 
       in C++ all characters are significant
    4. in C, you can call main() recursively, not in C++
    5. in C you cannot take the address of a register variable. in C++ you can
    6. in C the type wchar_t is defined with typedef. In C++ wchar_t is a keyword
    7. in C++, a function definition can explicitly take 0 to say that input
       argument is ignored. not in C.
         int except_handler(0)
    	 {
    	 
    	 }