/*
 * This program is designed to show some nuances of C++ syntax,
 * reference usage, and the various methods in a class (copy
 * constructor, operator=(), etc.).
 *
 * The addresses you get when running this program will most likley
 * differ; that is expected.  In fact, the addresses will differ upon
 * successive executions of the program.  If you are compiling this on
 * a 64-bit machine, you may want to use the -m32 flag to g++ to
 * compile it in 32-bit mode -- this keeps the pointers to 32 bits (as
 * opposed to 64 bits).
 *
 * Copyright (c) 2012 by Aaron Bloomfield (aaron@virginia.edu).  All
 * rights reesrved.
 */

#include <iostream>
using namespace std;

//--------------------------------------------------
// this would be in test.h

class test {
    static int idcount;
    const int id;
    int value;
public:
    test();
    test(int v);
    test(const test& x);
    ~test();
    test& operator=(const test& other);
    friend ostream& operator<<(ostream& out, const test& f);
};

//--------------------------------------------------
// this would be in test.cpp

int test::idcount = 0;

test::test() : id (idcount++), value(0) {
    cout << "calling test(); object created is " << *this
         << "; address is " << this << endl;
}

test::test(int v) : id (idcount++), value(v) {
    cout << "calling test(" << v << "); object created is " << *this
         << "; address is " << this << endl;
}

test::test(const test& x) : id(x.id), value(x.value) {
    cout << "calling test(const test&) on " << *this << "; address is " << this << endl;
}

test::~test() {
    cout << "calling ~test() on " << *this << endl;
}

test& test::operator=(const test& other) {
    cout << "calling operator=(" << other << ")" << endl;
    test *tmp = new test(other);
    return *tmp;
}

ostream& operator<<(ostream& out, const test& f) {
    out << "test[id=" << f.id << ",v=" << f.value << "]";
    return out;
}


//--------------------------------------------------
// this would be in main.cpp

test bar(test param) {
    return test(10);
}

int main() {

    // this does NOT create a test object; instead, it creates a function
    // called a() that has no body (and thus C++ defaults it to return
    // 1).  Note that there is no output statement from the default
    // constructor.
    test a();
    cout << "attempted to create a: " << a << endl;
    // output:
    // attempted to create a: 1

    // this does  create a test object, calling  the default constructor.
    // Since  putting parenthesis in  there for  creating an  object and
    // calling  the  default  constructor  has another  meaning  in  C++
    // (specifically,  creating  a  function),   we  have  to  omit  the
    // parenthesis.
    cout << "----------------------------------------" << endl;
    test aa;
    cout << "created aa: " << aa << endl;
    // output:
    // ----------------------------------------
    // calling test(); object created is test[id=0,v=0]; address is 0xff852a50
    // created aa: test[id=0,v=0]

    // this creates a test object, calling the specific constructor that
    // takes in a single int value
    cout << "----------------------------------------" << endl;
    test b(1);
    cout << "created b: " << b << endl;
    // output:
    // ----------------------------------------
    // calling test(1); object created is test[id=1,v=1]; address is 0xff852a48
    // created b: test[id=1,v=1]

    // this creates two test objects via pointers and new
    cout << "----------------------------------------" << endl;
    test *c = new test(2);
    cout << "created *c: " << *c << " at " << c << endl;
    test *d = new test;
    cout << "created *d: " << *d << " at " << d << endl;
    // output:
    // ----------------------------------------
    // calling test(2); object created is test[id=2,v=2]; address is 0xa009008
    // created *c: test[id=2,v=2] at 0xa009008
    // calling test(); object created is test[id=3,v=0]; address is 0xa009018
    // created *d: test[id=3,v=0] at 0xa009018

    // subroutine invocation.  The copy constructor is invoked when the
    // actual parameter is copied into the formal parameter.  The
    // subroutine then creates test[id=4,v=10].  The parameter that was
    // copied into the subroutine (via the copy constructor) is then
    // destructed upon termination of the subroutine.
    cout << "----------------------------------------" << endl;
    cout << "about to invoke subroutine..." << endl;
    test e = bar(*c);
    cout << "finished invoking subroutine..." << endl;
    // output:
    // ----------------------------------------
    // about to invoke subroutine...
    // calling test(const test&) on test[id=2,v=2]; address is 0xff852a38
    // calling test(10); object created is test[id=4,v=10]; address is 0xff852a40
    // calling ~test() on test[id=2,v=2]
    // finished invoking subroutine...

    // because this assignment happens in the same statement as the
    // declaration, it invokes the copy constructor
    cout << "----------------------------------------" << endl;
    test f = b;
    // output:
    // ----------------------------------------
    // calling test(&test) on test[id=1,v=1]; address is 0xff852a30

    // we are only deleting one of the dynamically created test objects
    cout << "----------------------------------------" << endl;
    cout << "about to delete a test object..." << endl;
    delete c;
    // output:
    // ----------------------------------------
    // about to delete a test object...
    // calling ~test() on test[id=2,v=2]

    // because this assignment does NOT happen in the same statement as
    // the declaration, the operator=() subroutine is called.
    cout << "----------------------------------------" << endl;
    cout << "assignment..." << endl;
    aa = b;
    // output:
    // ----------------------------------------
    // assignment...
    // calling operator=(test[id=1,v=1])
    // calling test(&test) on test[id=1,v=1]; address is 0xa009008

    // upon termination of the main() function, all statically created
    // test objects are deallocated.
    cout << "----------------------------------------" << endl;
    cout << "about to leave main..." << endl;
    // output:
    // ----------------------------------------
    // about to leave main...
    // calling ~test() on test[id=1,v=1]
    // calling ~test() on test[id=4,v=10]
    // calling ~test() on test[id=1,v=1]
    // calling ~test() on test[id=0,v=0]

    // Note that d was created via dynamic memory allocation, so it is
    // not deallcoated by the program.  The operating system will clean
    // up the used memory, but it does not call the destructor on d.
}