TweetFollow Us on Twitter

January 94 - C++ for Gurus

C++ for Gurus

Jeff Alger

Jeff Alger is a consultant and author internationally recognized as an authority in object-oriented methods and technologies. He is coauthor of "Developing Object-Oriented Software for the Macintosh." The material in this article was inspired by his experiences developing a new seminar, "Advanced C++," now taught by him at Apple Computer Developer University. The course deals with advanced architectures for C++ programs. For information contact Developer University at (408)974-4897 or (408)974-6215.

There is a growing community of disenfranchised C++ gurus out there, and you may be among them; if so, this article is for you. No, I'm not talking to you beginning and intermediate C++ programmers; you have dozens of books, loads of training classes, multimedia-based self-help courses, magazines, mentors and cocktail parties galore. Not that one more cocktail party would be a bad idea, mind you, it's just that for one of the few times in my otherwise spotless diplomatic career, I'm going to be politically incorrect and send you packing.

For just this once, I'm talking to the rest of you, and you know who you are. The sort of person to whom an "overloaded function" isn't a boring social affair, an "overloaded operator" doesn't work for the phone company and a "collection class" isn't a seminar on raising money for charity. You cite the ARM frequently in casual conversation (always pronounced "arm"), at least half the time correctly but who cares because no one else in the room knows what the acronym means and you're not about to tell them.1 You're the sort who doesn't read those "sissy" books and, as a result, haven't found any books about C++ worth reading since you browsed through the index of Lippman's book after starting your first C++ compile.2,3 You don't just program in C++, your ideas flow onto a digital canvas using brushes and a palette from Bell Labs. Well, you have rights, too, and it's about time someone did something to keep you and others like you from going stark, raving mad. To anyone I haven't thoroughly offended yet, glad to meet you, come on in. Welcome to group therapy for C++ gurus. This is the first and possibly the last article in a series on how to learn to stop worrying and love C++. It is an outgrowth of a new three-day course I developed recently and now teach at Apple Computer's Developer University, titled "Advanced C++" and targeted, not at C++ programmers, but at C++ architects.

The Zen of C++

The natural course of one's understanding of C++ is like rising on an elevator, with each floor decorated quite differently from the one below.

Ding! First floor. C++ is a more reasonable C, strongly typed as long as you don't fool around too much, and, hey, how about those nifty // comments? All the C programmers that didn't want to go into management really needed a career path, and Bjarne Stroustrup, bless his soul, dreamed up a doozy.

Ding! Second floor. C++ is a decent but not great object-oriented language that happens to run blindingly fast by comparison to others. It's politically correct and sure to get your project funded by top management. Heck, they might double your budget if you mention the language often enough in your proposal. That's just as well, because no one really knows how to estimate and manage C++ projects and as to tools… lot of weather we're having, eh?

Ding! Top floor, everybody out. Hey, where did everyone go? Sure is drafty up here. C++ is really "yacc++,"4 not so much a language as a way of creating your own languages, elegant not for its simplicity (like "jumbo shrimp," the terms "C++" and "simple" grate on the ears when used in the same sentence) but because of its potential. Lurking behind every gnarly design problem is a nifty idiom, a nice little twist to the language that makes the problem melt away like the Wicked Witch of the West without her umbrella. That idiom solves the problem as elegantly as a real language like Lisp or Smalltalk would, but without causing smoke to rise from your CPU and the stock of companies that manufacture memory chips to rise on Wall Street. C++ isn't a language, it's an experience, a mind-altering drug.

There's that word again, "elegant." There is a Zen to designing for C++: You have to stop trying so hard to be elegant in order to achieve true elegance. From its brutish ancestor C it inherits not only compilation efficiency but also a conventional block structured grammar and a terse syntax for commonly used forms. It's got nouns, verbs, adjectives and lots of slang, like

cout<<17<<endl<<flush;

C++ programmers have been cowed into embarrassment by the language purists. The purists think that distinguishing a simple variable from a function call from a macro using nothing but parentheses is a surefire crowd-pleasing feat of prestidigitation. In real life the crowd only applauds languages in which different ideas look different. The "simple, consistent" languages have never gained much of a following outside academia, while block-structured languages have drawn all the crowds. This shouldn't be a surprise; research in linguistics predicts that learning time should be shorter and reading comprehension and retention much higher for languages that have all those supposedly "bad" properties. "i++" really is more "readable" by any verifiable measure than "i := i + 1;" and people really do have an easier time reading "x = 17 + 29;" than "(setq x (+ 17 29))". This has nothing to do with the design of the programming language and everything to do with how we are designed. C++ is ugly in large part because we are ugly. Learn to know and love the quirks and stop worrying about mathematical consistency and you are on the road to elegance in C++.

Like Lisp, Smalltalk, and other dynamic languages (and unlike C), C++ provides the hooks needed to manipulate low-level behavior of the compiler. You can make up your own data types and fool the compiler into adopting them as its own progeny. You can control how functions are called, how data members are accessed, how memory is allocated and deallocated and how and when things get initialized. And all without sacrificing efficiency or type safety (often). Unlike those other languages, a C++ program will merrily crash and burn if you use this power the wrong way. Even if it doesn't, your fellow programmers might if you don't find just the right C++ idiom for a complicated design. Deadalus and his son, Icarus, escaped imprisonment on Crete by fashioning wings out of feathers and wax. The wings helped Deadalus, the master architect, soar to distant shores. In the arms of his brash son, they led to death when he flew too close to the sun and melted the wax. Now that I think of it, there is more irony than I intended in that analogy: Deadalus was also the designer of the Labyrinth, which was so complex no one could find the way out once imprisoned. Hmm. Perhaps a more contemporary analogy is needed. Every time you use these low-level C++ features it's like the bumbling detective Sledge Hammer from the 80's TV series saying to the compiler, "Trust me, I know what I'm doing." The compiler rolls its eyes and plays along.

C++ is intriguing because of its inherent contradictions. Powerful because of tools that are easily abused. An extensible programming environment without compromising space or speed. Elegant in one set of hands while dangerous in others. Simple and complex at the same time. After years of using it, you still can't make up your mind whether to admire it or walk away in disgust. Yet, to the truly expert, there are concepts that underlie the use of the language that tip the scales in its favor. Certain architectural paradigms go best with specific features of the language; mismatch the two and the result is chaos, but get the right combination and the result is… well, elegant.

Three Great Ideas in C++

It seems to the uninitiated that mastering C++ is really about getting your own bag of tricks like the ones carried by the masters. Some of these tricks are general object-oriented design principles. The rest are specific to C++ and revolve around how to use its unique strengths and work around its unique shortcomings. To my mind, there are three basic paradigms of C++ architecture that organize these tricks into a coherent framework.
  • Indirection
  • Homomorphic type hierarchies
  • Memory spaces

Each is supported by specific syntax and features of C++ and the three principles work together to solve an amazing variety of problems. There are other C++ design principles one could add to the list, but these are the core of any advanced C++ design.

Indirection covers a wide variety of individual topics, but the concept is the same throughout: some client object makes a request of a second object, which turns right around and delegates the work to a third object. The object in the middle is where the indirection takes place. Some might argue that this is almost the dictionary definition of delegation, one of the bulwarks of general object-oriented design, but in C++ the idioms one uses with this concept and the language support for it goes beyond what is considered delegation in other languages. For example, the object-in-the-middle, which I shall call the smart pointer for the time being, may determine where in memory, on disk, or in the network the object resides; when it gets destroyed; whether it may be updated or whether it is read-only; whether it even exists or whether it is simply an abstract location in some collection or memory space waiting to be assigned to. All without the active cooperation of the target object, which may be completely oblivious to all this grubby, low-level activity.

A homomorphic type hierarchy is one in which all derived classes share the same public interface as their common abstract base class. In fact, the abstract base class is usually as pure as the driven snow: all of its methods are pure virtual placeholders. This sounds simple enough, but in C++ there are a number of powerful language and design idioms that are enabled by the paradigm.

The idea of a memory space is enabled by your ability to overload operators new and delete and certain other very language-specific features. The concept is that an object may be allocated, not just randomly in a compiler-assigned location in memory, but as part of an implicit collection of objects not directly visible to the application. The simplest example is preallocating an array big enough for 1000 Foo's, then overloading operator new to allocate from an unused slot and operator delete to return the Foo to a free space list. But this is the plumber's view of architecture; the concept of a memory space builds on these low-level mechanics to become itself one of the central tools of the C++ designer. It is difficult to separate these concepts, since each borrows from the other two. But together they solve some of the most daunting problems of design in just a few lines of code. In fact, maybe that should be a standard by which to judge how well you have used the concepts: more than a couple of hundred lines and a giant hook pulls you off the stage. In the "Advanced C++" course several of the labs would normally merit a few weeks each just for design in a typical project. While the labs aren't exactly obvious, the solutions involve from dozens to a couple hundred lines of code and actually flow very logically from the coding and design idioms presented. The labs include

  • An "undo" facility that is easily extended into multi-user transaction processing;
  • An high-performance, industrial-strength garbage collection scheme for use with existing classes; and
  • Distributing objects over a network.

These all build toward what is almost an anticlimax, the use of all three design concepts to facilitate reuse. In the remainder of this article I will skim the surface of one of these three concepts, indirection.

Indirection

Many of the techniques used by the C++ masters revolve around the idea of indirection. Consider the following code fragment.
class Foo {
public:
    void MemberOfFoo();
};

Foo* aFoo = new Foo;
aFoo->MemberOfFoo();

Forget you ever knew anything about C and the lowly ->, and take a fresh look at the subject. This is the built-in "operator->" being applied to a built-in pointer "class," in this case the address held in aFoo. The built-in operator-> is available for use with any address whose type is "pointer to struct or class." In effect, you are indirectly referring to one object, the Foo, using another, the pointer. This is as far as built-in indirection goes, but you can extend the idea of operator-> through the magic of operator overloading. Here is a simple example of something known as a "smart pointer" class.

class PFoo {
private:
    Foo* fFoo;
public:
    PFoo(Foo* f): fFoo(f) {}
    Foo* operator->() { return fFoo; }
};

PFoo pf(new Foo);
pf->MemberOfFoo(); // Works!

Here the class PFoo slips into your program in place of the built-in "class" Foo*. When the compiler sees operator->, it does not automatically spit out the member requested on the right-hand side; instead, it looks to see whether the left-hand side is a built-in type (an address) or a user-defined type. If user-defined, it invokes the overloaded operator->() and repeats the process with the return value as the new left-hand side. Of course, if you are silly enough to fail to overload operator-> or to return the address of something other than a struct or class, the compiler will reach out and slap your hand and refuse to budge. In this case, pf->() returns a Foo*, which is a legal left-hand side to the built-in operator->, so the compiler is happy and we're happy.

There is no space or performance overhead to this indirection. What's the size of a smart pointer? The sum of its data members (there are no virtual member functions and, therefore, no vtable pointer to clutter up the object). In this case, an instance of PFoo is the same four bytes as a Foo*; it can be passed by value in a machine register just as efficiently as an integer or anything else. What about performance? All methods are trivial inlines that any good C++ compiler should handle as efficiently as using a built-in Foo* directly. At the least we haven't done any damage. But so far, we really haven't done anything we couldn't have done with the built-in operator, either. How many times have you gnashed your teeth over the old dereferencing-nil problem? Here is a minor variation on the theme.

inline Foo* PFoo::operator->() {
#ifdef DEBUGGING
    if (!fFoo) {
        cerr<<"*** NULL PFoo ***"<<endl;
    fFoo = new Foo; // Create a dummy instance
    }
#endif
    return fFoo;
}

Now we have a smooth way to handle the problem: spit out an error message and return a surrogate object to allow the program to keep limping along, at least in debug mode. There are further variations on this, such as storing a single default surrogate as a static data member of the class PFoo just so you can return it to wayward clients, or returning a derived class of Foo all of whose member functions scream loudly every time they are accessed.

Now suppose that Foo is really an abstract base class like this.

class Foo {
protected:
    Foo() {} // Cannot be directly instantiated
public:
    virtual void MemberOfFoo()=0;
    virtual int AnotherMember()=0;
    // ...
};

The client of the pointer now need have no idea which derived class of Foo is actually contained in the smart pointer. In fact, it is simple to switch the object pointed to at run time. "That's nothing," you may say, "I can change the address contained in a pointer variable, too." True, but can you train your pointers to do this?

class PFoo {
private:
    Foo* fFoo;
public:
    PFoo(Foo* f): fFoo(f) {}
    const Foo* operator->() const { return fFoo; }
    Foo* operator->() { // Warning! Non-const access!
        if (fFoo->IsConst())
            fFoo = fFoo->UpdateCopy(); // A new one I can change
        return fFoo;
    }
};

Presumably there is some optimal representation of Foo when it is being used in a read-only (const) fashion. If someone requests a member in a non-const way, the smart pointer automatically replaces the read-only version with a writeable version. In fact, with an extra level of indirection this example can be reworked so that the object itself need not provide the method support shown here (IsConst() and UpdateCopy()); that logic can be entirely contained in read-only and read-write pointers. That extra effort is rewarded; this architecture can then be slipped into an existing program that uses smart pointers. It would be extraordinarily difficult, however, to make this sort of change late in the game in a program organized around *s. One of the first steps on the road to C++ elegance is to learn to mumble smart pointers in one's sleep, using them routinely out of the vague sense that they might come in handy someday.

I am only hinting at a general strategy here: read-only pointers as distinct from read-write pointers. These wrap the object and largely insulate it from knowledge about how it is being used. For example, in a distributed object system one may use a local copy for read-only purposes but retrieve the "master" copy in order to perform updates.

When a smart pointer is in one-to-one correspondence with the object it points to, the terms "master pointer," "envelope" and "handle" have all been used by various authors. I prefer the term "master pointer" because I have other uses for the other terms. Here is a simple example that illustrates master pointers and the recursive use of operator->.

class PFoo { // This acts as a "master pointer"
private:
    Foo* aFoo;
public:
    PFoo(): aFoo(new Foo) {}
    PFoo(args):aFoo(new Foo(args)) {}
    PFoo(const PFoo& pf):aFoo (new Foo(*pf.aFoo)) {} // Copy contents
    PFoo& operator=(const PFoo& pf); // See below
    ~PFoo() { delete aFoo; }
    Foo* operator->() const { return aFoo; }
};
inline PFoo& PFoo::operator=(const PFoo& pf) {
    if (this == &pf) return *this;
    delete aFoo;
    aFoo = new Foo (*pf.aFoo);
    return *this;
}

class HFoo {
private:
    PFoo& fMasterPtr;
public:
    HFoo(PFoo& pf):fMasterPtr(&pf) {}
    Foo* operator->() const { return fMasterPtr; }
}

PFoo pf; // Creates master pointer plus instance of Foo
HFoo hf(pf); // Handle to the Foo

hf->MemberOfFoo(); // Works! Doubly interpreted: HFoo and PFoo

Here the direct pointer class, PFoo, is in one-to-one correspondence with the object it points to. When the master pointer is deleted, its destructor also deletes the object pointed to. operator-> is recursively interpreted twice so that the user of an HFoo need not do any explicit dereferencing. HFoo is now analogous to a "handle" in the Macintosh or Windows environments; it refers to the object indirectly through a master pointer. Unlike those environments, however, the dereference is done without compromising type safety and without relying on a single model of how objects are created, stored, and destroyed. In fact, those memory managers are really just a special case, a specific implementation of this design paradigm in C++.

Master pointers can be used for a wide variety of simple memory management strategies. operator-> can, for example, create the object on the fly if it doesn't already exist, perhaps reading it from a database or obtaining a copy from another process elsewhere on the network. Handles are the key to most advanced memory management strategies, including reference counting and the many variations of mark-and-sweep and generational garbage collection. It can also be extended to elegantly handle distribution of objects over multiple processes, computers, and storage media. In the seminar, we take this one step further, designing each master pointer to automatically maintain two copies of the target object: one for current access and one for undo.

One of the more intriguing and less obvious uses of indirection arises from the design of collection classes.

Arrays and operator[]

operator[] is amazingly versatile in ways most C++ programmers never consider. Here is a simple application: an array that checks its bounds when its elements are accessed.
class ArrayOfFoo {
private:
    int fSize;
    Foo* fContents;
    static Foo* fgSurrogate; // A dummy for out-of-bounds indices
public:
    ArrayOfFoo():fSize(0),fContents(nil) {}
    ArrayOfFoo(int size):
        fSize(size),fContents((Foo*)(new void*[size]) {}
    ~ArrayOfFoo() { delete fContents; }
    Foo*& operator[] (int index) {
        return (index<0 || index>=fSize)
            ? fgSurrogate : fContents[index];
        }
};

// In a .cp file somewhere
Foo* ArrayOfFoo::fgSurrogate = nil;

The return value from operator[] is a Foo*&, that is, a reference to an address of a Foo. This allows the result of operator[] to be used as the left-hand side of an assignment, among other things.

anArray[10] = aFoo;

If efficiency is more important than robustness, simply surround the fSize data member and the range-checking logic with #ifdefs, and you have an array identical in size and performance to a standard C-style array of pointers. But this only scratches the surface of operator[]. It can be overloaded to take non-integral arguments. Here is the outline of an association class that maintains a set of pairs (String, String), where the first string acts as a key and the second, the unique value associated with that key.

class Association {
public: // Implementation details spared here
    const String& operator[] (const String& key);
};

String value = anAssociation[someString];

This is much more elegant and expressive of the designer's intent than using a purely method-based interface like

String value = anAssociation.Lookup(someString);

Operator[] can be overloaded to accept any argument type, as for String in this example, with the sole restriction that it can only accept one argument. Thus, the following is not legal.

class WontWork {
public:
    Foo& operator[] (int x, int y); // Only one argument allowed
};

This isn't as much of a problem as it would appear, since one can easily create classes or structs that simulate a point in n-dimensional array space.

struct Index {
    int fx;
    int fy;
    Index(int x, int y):fx(x), fy(y) {}
    Boolean operator== (const Index& i) 
        { return fx==i.fx && fy==i.fy; }
};

class WorksFine {
public:
    Foo& operator[] (Index i);
};

anArray[Index(17,29)]; // Uses an anonymous instance of Index

It is also possible to overload operator[] more than once in a single class. Why would you want to do that? Perhaps you have a need to index two ways.

class StringArray {
public:
    const String& operator[] (int index);
    int operator[] (const String&);
};

The first operator[] maps from an integral index to the string at that location. The second does the opposite: given a string, it returns the index of that string in the array. (Presumably some value such as -1 is set aside to return when the string does not occur in the array.) Once one gets used to overloading operator[], it becomes invaluable as a way to encapsulating collections of all sorts, not just arrays. However, collections that do not simply span indices from 0 to N require a little more attention than simply overloading operator[].

A Sparse Array Class

One of the most basic data structures is the sparse array. This is a matrix most of whose cells are empty. To represent this as an NxM (or even higher dimensions) array of cells would be a terrible waste of space, so a variety of lower-level data structures are used to simulate the array: linked lists, hash tables, binary trees, and just about any other exercise from your Introduction to Data Structures course in college. We aren't concerned here with the implementation details but rather with how to best leverage C++ language features to isolate clients from those details. To illustrate the ideas, here is a brute-force implementation using a linked list of cells.
class SparseArray {
private:
    struct Node {
        Index fIndex; // Uses Index structure above
        Foo* fContents;
        Node* fNext;
        };
    Node* fCells;
public:
    SparseArray(): fCells(nil) {}
    Foo* operator[](Index i) {
        Node* n = fCells;
        while (n)
            if (n->fIndex == i) // Why we overloaded == for Index!
                return n->fContents;
            else n = n->fNext;
        return nil; // Not found
        }
};

aFoo = anArray[Index(17,29)]; // Works

This is fine for accessing cells of the array, but how do we add new cells or reassign existing ones? The operator[] we have created won't work as the left-hand side of an assignment because it is a Foo*, not a Foo*&.

anArray[Index(17,29)] = aFoo; // Will not work

We could create some special functional interface, but that would mean breaking the illusion for the client that this is a normal array. Is there a way to use operator[] as the left-hand side of an assignment with this and other pseudo-arrays? It turns out the answer is Yes, but we have to introduce a new design paradigm for the purpose.

Cursors

Let's try again.
class ArrayCursor;
 
class SparseArray {
friend class ArrayCursor;
public: // private: ***The Semantec work-around***
   struct Node {
   Index fIndex; // Uses Index structure above
   Foo* fContents;
   Node* fNext;
   Node(Index i, Foo* content, Node* next)
   : fIndex(i), fContents(content), fNext(next) {}
   };
   Node* fCells;
public:
   SparseArray(): fCells(nil) {}
   ArrayCursor& operator[](Index i);// inline moved below ArrayCursor
};
 
class ArrayCursor {
friend class SparseArray;
private:
   SparseArray& fArray;
   Index fIndex; // Index this cursor represents
   SparseArray::Node* fNode; // Non-nil means the index exists
   // Constructors are private; only SparseArray can create these
   ArrayCursor(SparseArray& array, Index i)
   : fArray(array), fIndex(i), fNode(nil) {}
   ArrayCursor(SparseArray& array, SparseArray::Node* node)
   : fArray(array), fIndex(node->fIndex), fNode(node) {}
public:
   ArrayCursor& operator=(Foo* foo) {
   if (!fNode) {
   fNode = new SparseArray::Node
   (fIndex,foo,fArray.fCells);
   fArray.fCells = fNode;
   return *this;
   }
   fNode->fContents = foo;
   return *this;
   }
};
 
inline ArrayCursor& SparseArray::operator[](Index i) {
   SparseArray::Node* n = fCells;
   while (n)
   if (n->fIndex == i) return ArrayCursor(*this,n);
   else n = n->fNext;
   return ArrayCursor(*this,i); // Not found
   }

Whoa! What's going on here? operator[] returns an ArrayCursor for an index that does not yet exist. This becomes the left-hand side of the assignment, so the ArrayCursor::operator= is invoked. This creates a new Node and adds the right-hand side as the contents of that cell. To the client this appears to be a simple array even though the internal details are anything but simple.

There are a couple of details glossed over here. For example, the following would not work.

anArray[Index(17,29)]->MemberOfFoo();

This and similar problems are simple to handle by overloading operator-> and adding an operator Foo* in the ArrayCursor class.

The concept of a cursor is not specific to the matrix form. A cursor can be used to represent any "position" in a collection, even a collection that is unordered. Cursors can also be used to represent positions in disk files or other processes and computers. C++ operators ->, [] and =, overloaded together, largely insulate clients from where the actual objects and collections are physically stored and what index structures are used to retrieve them.

From Cursors to Iterators

I said earlier that a cursor is a new design idiom, but it is easy to connect it to one you are probably already familiar with: the iterator. The basic concept of an iterator is illustrated by the following code fragment.
class Collection {
public:
    class Iterator {
    public:
        Boolean More();
        Foo* Next();
    };
    Iterator* ProvideIterator(); // Returns an Iterator
};

Collection::Iterator* iter = aCollection->ProvideIterator();

while (iter->More())
DoSomethingToAFoo(iter->Next());

Here we have assumed that the collection contains Foo*s; more generally, one would use a template to genericize these classes, but the treatment here is otherwise general. Note that you ask the collection object to hand you the iterator, rather than directly instantiating it yourself. This allows derived classes of Collection to return derived classes of Iterator without the client having any knowledge that derived classes are involved.5 There are lots of variations on the theme, such as controlling the order or range of the iteration.

How are iterators implemented? One straightforward approach is to simply extend the collection's cursor class to add the More and Next member functions! This combination provides a bonus in the box of candy corn: assignment to the current location of the iterator/cursor is supported automatically. A closely related design strategy is to create an iterator class that contains a cursor as a data member.

Cursors and iterators aren't just oddly shaped widgets in the bag of C++ tricks. If you look carefully, you will see that they recycle the idea of indirection. A cursor with an overloaded operator-> is just a "smarter" pointer of sorts. The same technique of pointing to something that isn't there yet finds wide application in other design problems: persistent objects, distribution, and caching, to name a few. This convergence of design ideas is typical of advanced C++ architectures.

Top Floor

There is a relatively small circle of experts in C++ and object-oriented design that understand and routinely apply these principles, creating what to others often seems black magic. As with all magicians, people hold them in a combination of reverence and distrust. However, the real problem is a lack of literature, for the techniques themselves are accessible to anyone with a solid background in C++ and software design. Hopefully now that C++ is entering its teen years we will see more attention paid to members of that frustrated underclass, the C++ architect. In future articles, I'll pay some attention to homomorphic type hierarchies and memory spaces.

Copyright ©1994 by Jeff Alger. All rights reserved.

  1. That's Bjarne Stroustrup's C++ Annotated Reference Manual, and if you had to ask perhaps you should go find one of those cocktail parties. No offense intended… I'll catch up with you later.
  2. Well, maybe James Coplien's Advanced C++ Programming Styles and Idioms caught your eye for a while, but much of that book is just too weird. I mean, how many people really care about how to use C++ to emulate Lisp programming? Do you really need to change the implementation of a method on the fly without shutting down a running C++ program? I admire the book, but it doesn't fill the void on advanced C++ usage.

    You don't mind footnotes, either.

  3. Oh, you're not a Unix hacker, either? That's the Unix-based "yet another compiler compiler," (cute, huh?) a do-it-yourself kit for implementing programming languages.
  4. Note for MacApp programmers: iterators in MacApp violate these rules and as a result are not quite as flexible as the strategy presented here.
 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

Fallout Shelter pulls in ten times its u...
When the Fallout TV series was announced I, like I assume many others, assumed it was going to be an utter pile of garbage. Well, as we now know that couldn't be further from the truth. It was a smash hit, and this success has of course given the... | Read more »
Recruit two powerful-sounding students t...
I am a fan of anime, and I hear about a lot that comes through, but one that escaped my attention until now is A Certain Scientific Railgun T, and that name is very enticing. If it's new to you too, then players of Blue Archive can get a hands-on... | Read more »
Top Hat Studios unveils a new gameplay t...
There are a lot of big games coming that you might be excited about, but one of those I am most interested in is Athenian Rhapsody because it looks delightfully silly. The developers behind this project, the rather fancy-sounding Top Hat Studios,... | Read more »
Bound through time on the hunt for sneak...
Have you ever sat down and wondered what would happen if Dr Who and Sherlock Holmes went on an adventure? Well, besides probably being the best mash-up of English fiction, you'd get the Hidden Through Time series, and now Rogueside has announced... | Read more »
The secrets of Penacony might soon come...
Version 2.2 of Honkai: Star Rail is on the horizon and brings the culmination of the Penacony adventure after quite the escalation in the latest story quests. To help you through this new expansion is the introduction of two powerful new... | Read more »
The Legend of Heroes: Trails of Cold Ste...
I adore game series that have connecting lore and stories, which of course means the Legend of Heroes is very dear to me, Trails lore has been building for two decades. Excitedly, the next stage is upon us as Userjoy has announced the upcoming... | Read more »
Go from lowly lizard to wicked Wyvern in...
Do you like questing, and do you like dragons? If not then boy is this not the announcement for you, as Loongcheer Game has unveiled Quest Dragon: Idle Mobile Game. Yes, it is amazing Square Enix hasn’t sued them for copyright infringement, but... | Read more »
Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »

Price Scanner via MacPrices.net

Verizon has Apple AirPods on sale this weeken...
Verizon has Apple AirPods on sale for up to 31% off MSRP on their online store this weekend. Their prices are the lowest price available for AirPods from any Apple retailer. Verizon service is not... Read more
Apple has 15-inch M2 MacBook Airs available s...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs available starting at $1019 and ranging up to $300 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at Apple.... Read more
May 2024 Apple Education discounts on MacBook...
If you’re a student, teacher, or staff member at any educational institution, you can use your .edu email address when ordering at Apple Education to take up to $300 off the purchase of a new MacBook... Read more
Clearance 16-inch M2 Pro MacBook Pros in stoc...
Apple has clearance 16″ M2 Pro MacBook Pros available in their Certified Refurbished store starting at $2049 and ranging up to $450 off original MSRP. Each model features a new outer case, shipping... Read more
Save $300 at Apple on 14-inch M3 MacBook Pros...
Apple has 14″ M3 MacBook Pros with 16GB of RAM, Certified Refurbished, available for $270-$300 off MSRP. Each model features a new outer case, shipping is free, and an Apple 1-year warranty is... Read more
Apple continues to offer 14-inch M3 MacBook P...
Apple has 14″ M3 MacBook Pros, Certified Refurbished, available starting at only $1359 and ranging up to $270 off MSRP. Each model features a new outer case, shipping is free, and an Apple 1-year... Read more
Apple AirPods Pro with USB-C return to all-ti...
Amazon has Apple’s AirPods Pro with USB-C in stock and on sale for $179.99 including free shipping. Their price is $70 (28%) off MSRP, and it’s currently the lowest price available for new AirPods... Read more
Apple Magic Keyboards for iPads are on sale f...
Amazon has Apple Magic Keyboards for iPads on sale today for up to $70 off MSRP, shipping included: – Magic Keyboard for 10th-generation Apple iPad: $199, save $50 – Magic Keyboard for 11″ iPad Pro/... Read more
Apple’s 13-inch M2 MacBook Airs return to rec...
Apple retailers have 13″ MacBook Airs with M2 CPUs in stock and on sale this weekend starting at only $849 in Space Gray, Silver, Starlight, and Midnight colors. These are the lowest prices currently... Read more
Best Buy is clearing out iPad Airs for up to...
In advance of next week’s probably release of new and updated iPad Airs, Best Buy has 10.9″ M1 WiFi iPad Airs on record-low sale prices for up to $200 off Apple’s MSRP, starting at $399. Sale prices... Read more

Jobs Board

Liquor Stock Clerk - S. *Apple* St. - Idaho...
Liquor Stock Clerk - S. Apple St. Boise Posting Begin Date: 2023/10/10 Posting End Date: 2024/10/14 Category: Retail Sub Category: Customer Service Work Type: Part Read more
*Apple* App Developer - Datrose (United Stat...
…year experiencein programming and have computer knowledge with SWIFT. Job Responsibilites: Apple App Developer is expected to support essential tasks for the RxASL Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Operations Associate - *Apple* Blossom Mall...
Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.