    #include <iostream>
    #include <cstdint>

    struct Object
    {
        int m_i;

        void event(const char* what, const char* where)
        {
            std::cout <<
                what<< " " << (void*)this <<
                " value " << m_i <<
                " via " << where <<
                std::endl;
        }

        // Construct an object with a specific value.
        Object(int i) : m_i(i)
        {
            event("Constructed", "Operator(int i)");
        }

        // This is called the copy constructor, create one object from another.
        Object(const Object& rhs) : m_i(rhs.m_i)
        {
            event("Constructed", "Operator(const Object&)");
        }

        // This is how to handle Object o1, o2; o1 = o2;
        Object& operator=(const Object& rhs)
        {
		    m_i = rhs.m_i;
            event("Assigned", "operator=");
			return *this;
        }

        // Handle destruction of an instance.
        ~Object() { event("Destructed", "~Object"); }
    };

    void foo1(Object o)
    {
        std::cout << "Entered foo1, my o has value " << o.m_i << std::endl;
        // poke our local o
        o.m_i += 42;
        std::cout << "I changed o.m_i, it is " << o.m_i << std::endl;
    }

    void foo2(Object* o)
    {
        std::cout << "Foo2 starts with a pointer, it's value is " << (uintptr_t)o << std::endl;
        std::cout << "That's an address: " << (void*)o << std::endl;
        std::cout << "m_i of o has the value " << o->m_i << std::endl;
        o->m_i += 42;
        std::cout << "I've changed it tho, now it's " << o->m_i << std::endl;
    }

    void foo3(Object& o)
    {
        std::cout << "foo3 begins with a reference called o, " << std::endl <<
            "which is sort of like a pointer but the compiler does some magic " << std::endl <<
            "and we can use it like a local concrete object. " <<
            std::endl <<
            "Right now o.m_i is " << o.m_i <<
            std::endl;
        o.m_i += 42;
        std::cout << "Only now, it is " << o.m_i << std::endl;
    }

    void foo4(Object*& o)
    {
        std::cout << "foo4 begins with a reference to a pointer, " << std::endl <<
            "the pointer has the value " << (uintptr_t)o << " which is " <<
            (void*)o <<
            std::endl <<
            "But the pointer points to an Object with m_i of " << o->m_i << std::endl <<
            "which we accessed with '->' because the reference is to a pointer, " <<
            "not to an Object." <<
            std::endl;
        o->m_i += 42;
        std::cout << "I poked o's m_i and now it is " << o->m_i << std::endl;
        // Now for something really dastardly.
        o = new Object(999);
        std::cout << "I just changed the local o to point to a new object, " <<
            (uintptr_t)o << " or " << (void*)o << " with m_i " << o->m_i <<
            std::endl;
    }

    int main()
    {
        std::cout << "Creating our first objects." << std::endl;
        Object o1(100), o2(200);

        std::cout << "Calling foo1 with o1" << std::endl;
        foo1(o1);
        std::cout << "back in main, o1.m_i is " << o1.m_i << std::endl;

        std::cout << "Calling foo2 with &o1" << std::endl;
        foo2(&o1);
        std::cout << "back in main, o1.m_i is " << o1.m_i << std::endl;

        std::cout << "Calling foo3(o2), which looks like the way we called foo1." << std::endl;
        foo3(o2);
        std::cout << "back in main, o2.m_i is " << o2.m_i << std::endl;

        std::cout << "Creating our pointer." << std::endl;
        Object* optr;
        std::cout << "Setting it to point to 'o2'" << std::endl;
        optr = &o2;
        std::cout << "optr now has the value " << (uintptr_t)optr <<
            " which is the address " << (void*)optr <<
            " which points to an Object with m_i = " << optr->m_i <<
           std::endl;

        foo4(optr);

        std::cout << "back in main, o2 has the value " << o2.m_i << std::endl <<
		    "and now optr has the value " << (uintptr_t)optr << std::endl <<
			"and optr->m_i is now " << optr->m_i <<
			std::endl;

        if (optr != &o2)
            delete optr; // otherwise we'd technically be leaking memory.
			
        return 0;
    }