C++ for Java programmers (9-14, 2004) A1 due Thursday how's it going? questions? turn-in instructions are in the newsgroup quiz answers are on the web, as are these notes grades will be in WebCT soon room change a week from Thursday: we'll be in 601 that day ---------------------------------- intro C++ is almost entirely backward compatible with C. Almost any ANSI/ISO-compliant C program will be accepted by a C++ compiler. A few deprecated features aren't supported (most notably functions without prototypes). Like C, C++ places a very heavy emphasis on execution speed. This has a variety of impacts, notably on storage management (no garbage collection), dynamic dispatch (off by default), and conventions for container classes (heavy use of templates to avoid run-time type checks). ---------------------------------- details no garbage collection you create things much as in Java my_type *my_ptr = new my_type(constructor_args); // note that new returns a C-like *pointer* When you are sure your program doesn't need something anymore, you have to explicitly delete it: delete my_ptr; If you accidentally *do* use the object again, Bad Things can happen. But if you accidentally forget to delete something you can't reach, you leak storage. Major pain in the butt. There are (non-standard) packages that try to add some limited form of garbage collection, but none of them are fully general, and the ones that come closest aren't fast. destructors These are the opposite of constructors. They're declared with a leading tilde, and no parameters. Mostly they're needed for explicit space deletion. Java can get by without them because it has garbage collection. Given the availability of destructors, C++ programmers have invented other clever uses for them, e.g. for locking: class lock { ... public: void acquire(); void release(); }; class mutex { lock *l_save; public: mutex(lock &l) {l_save = &l; l.acquire();} ~mutex() {l_save->release();} }; lock L; { // critical section; to be executed atomically mutex m(L); // mutex is a dummy object whose constructor acquires // the lock passed as an argument, and then keeps a // pointer to this lock in a private data member. // bracketed code // at end of scope, mutex destructor automatically releases L } values, pointers, and refs As in C, C++ variables are, by default, values, NOT references. If you want to point to things, create recursive structures, etc., you need pointers or references. Pointers are inherited from C. References are like variables of class type in Java, except that you can't change what they refer to. class list_node { int a; list_node *next; // pointer public: list_node() {a = 0; next = 0;} // constructor int get_val() {return a;} }; list_node f; // value variable: space is in current stack // frame (or global if not inside a function) list_node *g = new list_node(); // pointer; g = &f; // now g points at f. If nothing else points at // what g used to point at, we've just leaked storage list_node &h = f; // reference; h = f; // copies WHOLE OBJECT; does NOT change what h // refers to. Note that assignment (=) of value variables copies their value. If the value has internal pointers, those are copied too; there is no automatic dynamic allocation, and the target data is not copied. So the standard library classes (and any complex classes that you create and want to work correctly) overload the assignment operator. Refs are particularly handy for parameters, where they allow you to change the values of arguments without using confusing pointer syntax. They're also handy for return values, because they let you apply operations to the thing that was returned, without messy pointer syntax, and without copying values: my_shape.rotate(45).zoom(150).move(0, -20); Refs are *implemented* much the same as pointers; it's just the syntax (and the safety) that is different. Note that if you have a pointer you have to use C-style pointer syntax to access members: f.get_val(); g->get_val(); == (*g).get_val(); h.get_val(); Also note what it means if you declare a class-typed variable without any initializer: foo f; In Java f would be a null reference. In C++ it's an in-line object for which the compiler calls the zero-argument constructor (which must exist, or you'll get a compile-time error). declarations v. definitions A declaration introduces a name in a scope. A definition tells the compiler everything it needs to know to implement the named thing. Sometimes you have a declaration without a definition. One example is mutually recursive classes: class foo; class bar { foo *fp; ... }; class foo { bar *bp; ... }; Unlike Java, C++ requires that things outside a class be declared before they are used. (Class members can be declared in any order.) out-of-line definitions In the simple example above, the bodies of the list_node constructor and the get_val method are provided in-line. The compiler will almost certainly implement them inline (no real subroutine call). You don't have to have to provide the method body (definition) in the class declaration, however: class list_node { int a; list_node *next; // pointer public: list_node(); int get_val(); }; Then elsewhere (even in another file) you can write list_node::list_node() { a = 0; next = 0; } int list_node::get_val() { return a; } The :: is called the "scope resolution operator". In Java '.' is used for this purpose. ::x means the x at global scope (as distinguished from any other more local x). It's also used to identify names in namespaces (more on that below) and to get hold of the original version of a method inside a re-declaration: class foo : bar { ... void glarch () { ... // do some bar-specific stuff foo::glarch(); // call the original version ... // do some more bar-specific stuff }; visibility C++ distinguishes among public protected private class members. Public means accessible to anybody. Private means just to members of this class. Protected means to members of this or derived classes. Rather than label each member in a class declaration (as in Java), C++ makes public:, private:, and protected: "labels" that cover all subsequent members (up to the next such label). If there isn't an initial label, it's "private:" by default, or "public:" if this is a struct instead of a class (that's the only difference between the two). C++ base classes can also be public, private, or protected. E.g. class circle : public shape { ... anybody can convert (assign) a circle* into a shape* class circle : protected shape { ... only members and friends of circle or its derived classes can convert (assign) a circle* into a shape* class circle : private shape { ... only members and friends of circle can convert (assign) a circle* into a shape* Here the colon corresponds to the Java "extends". namespaces (similar to packages in Java) These are good old-fashioned modules, except that they can span multiple files. They have a simple import/export mechanism: everything is exported with a "qualified" name (namespace::object). This avoids name conflicts in user modules. To get around the nuisance of using fully qualified names, users can import them: using foo::my_object; // Within current scope I can now simply refer to my_object. using namespace foo; // import eveything from foo header files As in C, programmers by convention declare interfaces (in an informal sense of the word) in .h files, leaving the full definitions to .cc files. Java extracts the interface information (what it needs to compile users of the abstraction) automatically from the single set of .java files. Which is better is a religious debate. All of the standard library classes are obtained by "#include"-ing the appropriate header file and then, if desired "using" appropriate names so you don't have to keep qualifying them with the namespace. For example: #include using std::string; Sometimes one has headers that may include each other. To avoid redefinitions of names, programmers commonly use the conditional compilation facility of the C/C++ preprocessor: #ifndef FOO_H #define FOO_H // body of header foo.h #endif initialization v. assignment not the same in C++! foo& foo::operator=(&foo) v. foo::foo(&foo) foo b; // calls no-arg constructor foo f = b; // calls one-arg "copy constructor". // This is syntactic sugar for // foo f(b); foo b, f; // calls no-arg constructor f = b; // calls operator= C++ requires that every object be initialized by a call to an constructor. The rules for doing this for expanded objects are quite complex. objects as members foo::foo(args) : base(args0), member1(args1), member2(args2) { ... args0, args1, args2, &c need to be specified in terms of args. The reason these things end up in the header of foo is that they get executed *before* foo's constructor does, and the designers consider it good style to make that clear in the header of foo::foo. Commonly the arg lists are singletons (for copy constructors), and the code foo::foo(a, b, c) : member1(a), member2(b) { ... might be replaced with foo::foo(a, b, c) { member1 = a; member2 = b; but this is NOT the same: the latter option calls zero-arg constructors for member1 and member2 *before* calling foo::foo(), and *then* calls operator=. Life is easier in Java because all object-typed variables are references. Data members of object types are simply initialized to null; you specify arguments to the constructors when you call new, explicitly. Arguments for the superclass constructor, if any, can be provided in a pseudo-call, which must be the first statement of the constructor: public child(a, b, c) { super(a, b); ... If you don't provide the super() call, the compiler inserts a call to the zero-arg constructor (which must exist). virtual functions Virtual functions in C++ use dynamic method binding: you don't know at compile time what type the object referred to by a pointer variable will be at run time. [ Simula also had virtual functions (all of which are abstract). In Smalltalk, Eiffel, Modula-3, and Java *all* member functions are virtual, so you don't need the keyword. ] Key question: if child is derived from parent and I have a parent* p (or a parent& p) that points (refers) to an object that's actually a child, what member function do I get when I call p->f (p.f)? By default in C++ I get p's f, because p's type is parent*. But if f is a virtual function, I get c's f. Also note: If a C++ virtual function has a "0" body in the parent class, then the function is said to be a "pure" virtual function and the parent class is said to be "abstract". In Java you prepend the method declaration with the "abstract" keyword. You can't declare objects of an abstract class; you have to declare them to be of derived classes. Moreover any derived class *must* provide a body for the pure virtual function(s) (unless it too is supposed to be abstract). Note that virtual functions make it much harder for the compiler to in-line function calls. For safety's sake, destructors should always be declared virtual. templates In Java (prior to J2SE 5) the only way to abstract over types is to use class Object, and then cast back to what you want: Stack s = new Stack(); s.push(new Integer(3)); s.push("hi, mom"); String str = (String) s.pop(); int i = (int) s.pop(); A nice side effect of this is that you can have a stack containing objects of multiple types, but the casts take time, at run time. You can do basically the same thing in C++, but it isn't safe, because there isn't a universal base class like Object from which you can do run-time-checked casts. Preferred practice uses templates to abstract over types at compile time. This means your containers are all homogeneous, but they're very fast: stack *s = new stack; s->push(3); s->push(4); int i = s->top(); s->pop(); // pop doesn't actually return anything int j = s->top(); s->pop(); In most cases, the C++ compiler creates a separate copy of the stack code specialized for whatever type you put inside. stack t; t.push("hi"); t.push("mom"); string i = t->top(); t->pop(); string j = t->top(); t->pop(); Note that there's a lot going on under the hood here. "string" is a standard template library class. A string variable has (in-line) space to hold, probably, a length and a pointer to the text (the exact contents are private). Parameter passing in C++ implicitly invokes appropriate constructors, so "hi" in line two is acceptable as an argument to push() because string has a constructor that takes a single C-string argument. BTW, J2SE 5 also has templates. They're significantly simpler (and less powerful); more on this later in the semester. const Modifier that can be placed on any declaration to indicate that the object is read-only. Doesn't change the code at all, just reduces the number of things that the compiler will allow. In other words, it's a compile-time verified assertion that allows you to catch certain errors earlier. Note the difference between char* const p; // constant pointer to character const char* p; // pointer to character that is constant const char* const p; // both As in C declarations, start at the object name, read right as far as possible w/out leaving a level of parentheses read left as far as possible w/out leaving a level of parentheses pop out a level of partheneses and repear const char* (*f)() // f is a pointer to a function returning a pointer to a // character that is constant BTW, const also gets used as a *trailing* modifier for methods: class list_node { ... int get_val() const {return a;} // here the 'const' means "does not modify the data members // of the object" }; casts static_cast // Unchecked -- asserts that you know what you're doing. // Generates code iff necessary to convert value of type compiler // thinks it has into type you want. Allowed only if conversion // makes sense. double d = 3.14; int i = static_cast(d); dynamic_cast // Checked. Allowed only for objects of classes with at least one // virtual function. Allows you to turn a pointer to a base // class into a pointer to a derived class. This is what casts are // usually used for in Java. my_parent_type *q = ... my_child_type *p = dynamic_cast(q); const_cast // Unchecked. Allows you to remove "const"ness, assuming you know // what you're doing. const char *p = "hello, world\n"; char *q = const_cast("hello, world\n"); reinterpret_cast // Allows you to reinterpret the bits of something, without any // conversion. Rules on what is allowed are somewhat complicated. long l = 0x1234; char *p = reinterpret_cast(l); Old style C casts are also allowed. They do a combination of the above. The rules for exactly what are quite complex. stream I/O Employ overloaded "shift" operators. The three standard streams are named cin, cout, and cerr: #include using std::cout; using std::cin; ... cout << "hello, mom" << "\n"; cout << "pi is about " << 3.14; // overloaded operator<< functions support lots of types, // and you can define more if you want int i; double d; cin >> i >> d; Streams are quite general. You can, for example, make them out of strings. This is from the PL/0 compiler (file scanner.cc), used for projects in prior years: #include using std::ostringstream; ... ostringstream ost; ost << "Invalid token. " << error_chars << " non-white characters ignored."; ib->issue_warning(loc, ost.str()); Formatting is handled via "pseudo-outputs" that print nothing, but modify the state of the stream, temporarily (for one output) or permanently (until changed again, or restored). The most useful of these is probably setw(width), which affects only the next output. Again from scanner.cc: #include #include using std::cout; using std::setw; using std::left; using std::ios_base; ... ios_base::fmtflags old_options = cout.flags(); cout << left << setw(20) << string(major_token_names[major]) + (minor_token_names[minor][0] ? (":" + string(minor_token_names[minor])) : "") << (text_handle ? *text_handle : ""); cout.flags(old_options); Here we need to save and restore stream flags, because "left" makes a permanent change (and we don't, in general, know what it was before). iterator objects In Java you may be used to saying for (Iterator i = myTree.iterator(); i.hasNext();) { Object o = i.next(); System.out.println(o.toString()); } In J2SE 5 you can abbreviate this for (Object o : myTree) { System.out.println(o.toString()); // note that the Iterator MUST be created by a method named "iterator" } In C++ the equivalent is for (tree_node::iterator n = my_tree->begin(); n != my_tree->end(); ++n) { cout << *n << "\n"; } This example illustrates a different way of thinking about iterator objects. Instead of thinking of them as objects that know how, when asked, to return a reference to the "next" thing, C++ programmers are encouraged to think of them as pointers. They are defined by overloading operator*, operator->, and operator++. The end() method returns an iterator that "points to" an object "off the end" of the sequence. All of the standard library containers export iterators. ---------------------------------- not going to talk about multiple inheritance allows a class to inherit data members and non-virtual methods from more than one base class (not going to cover this today) friends (this either) allows you to break the standard visibility rules functions classes operator overloading default parameters classes as members Cf. "inner" classes in Java Objects of an inner class can only be created and used inside (non-static) methods of the outer class. They therefore "belong" to a given instance of the outer class, and can access data members of that class. This capability provides much (most?) of the power of nested subroutines, which C++ and Java lack.