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

Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »
Embark into the frozen tundra of certain...
Chucklefish, developers of hit action-adventure sandbox game Starbound and owner of one of the cutest logos in gaming, has released their roguelike deck-builder Wildfrost. Created alongside developers Gaziter and Deadpan Games, Wildfrost will... | Read more »
MoreFun Studios has announced Season 4,...
Tension has escalated in the ever-volatile world of Arena Breakout, as your old pal Randall Fisher and bosses Fred and Perrero continue to lob insults and explosives at each other, bringing us to a new phase of warfare. Season 4, Into The Fog of... | Read more »
Top Mobile Game Discounts
Every day, we pick out a curated list of the best mobile discounts on the App Store and post them here. This list won't be comprehensive, but it every game on it is recommended. Feel free to check out the coverage we did on them in the links below... | Read more »
Marvel Future Fight celebrates nine year...
Announced alongside an advertising image I can only assume was aimed squarely at myself with the prominent Deadpool and Odin featured on it, Netmarble has revealed their celebrations for the 9th anniversary of Marvel Future Fight. The Countdown... | Read more »
HoYoFair 2024 prepares to showcase over...
To say Genshin Impact took the world by storm when it was released would be an understatement. However, I think the most surprising part of the launch was just how much further it went than gaming. There have been concerts, art shows, massive... | Read more »

Price Scanner via MacPrices.net

Amazon is offering a $100 discount on every M...
Amazon is offering a $100 instant discount on each configuration of Apple’s new 13″ M3 MacBook Air, in Midnight, this weekend. These are the lowest prices currently available for new 13″ M3 MacBook... Read more
You can save $300-$480 on a 14-inch M3 Pro/Ma...
Apple has 14″ M3 Pro and M3 Max MacBook Pros in stock today and available, Certified Refurbished, starting at $1699 and ranging up to $480 off MSRP. Each model features a new outer case, shipping is... Read more
24-inch M1 iMacs available at Apple starting...
Apple has clearance M1 iMacs available in their Certified Refurbished store starting at $1049 and ranging up to $300 off original MSRP. Each iMac is in like-new condition and comes with Apple’s... Read more
Walmart continues to offer $699 13-inch M1 Ma...
Walmart continues to offer new Apple 13″ M1 MacBook Airs (8GB RAM, 256GB SSD) online for $699, $300 off original MSRP, in Space Gray, Silver, and Gold colors. These are new MacBook for sale by... Read more
B&H has 13-inch M2 MacBook Airs with 16GB...
B&H Photo has 13″ MacBook Airs with M2 CPUs, 16GB of memory, and 256GB of storage in stock and on sale for $1099, $100 off Apple’s MSRP for this configuration. Free 1-2 day delivery is available... Read more
14-inch M3 MacBook Pro with 16GB of RAM avail...
Apple has the 14″ M3 MacBook Pro with 16GB of RAM and 1TB of storage, Certified Refurbished, available for $300 off MSRP. Each MacBook Pro features a new outer case, shipping is free, and an Apple 1-... Read more
Apple M2 Mac minis on sale for up to $150 off...
Amazon has Apple’s M2-powered Mac minis in stock and on sale for $100-$150 off MSRP, each including free delivery: – Mac mini M2/256GB SSD: $499, save $100 – Mac mini M2/512GB SSD: $699, save $100 –... Read more
Amazon is offering a $200 discount on 14-inch...
Amazon has 14-inch M3 MacBook Pros in stock and on sale for $200 off MSRP. Shipping is free. Note that Amazon’s stock tends to come and go: – 14″ M3 MacBook Pro (8GB RAM/512GB SSD): $1399.99, $200... Read more
Sunday Sale: 13-inch M3 MacBook Air for $999,...
Several Apple retailers have the new 13″ MacBook Air with an M3 CPU in stock and on sale today for only $999 in Midnight. These are the lowest prices currently available for new 13″ M3 MacBook Airs... Read more
Multiple Apple retailers are offering 13-inch...
Several 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... Read more

Jobs Board

Relationship Banker - *Apple* Valley Financ...
Relationship Banker - Apple Valley Financial Center APPLE VALLEY, Minnesota **Job Description:** At Bank of America, we are guided by a common purpose to help Read more
IN6728 Optometrist- *Apple* Valley, CA- Tar...
Date: Apr 9, 2024 Brand: Target Optical Location: Apple Valley, CA, US, 92308 **Requisition ID:** 824398 At Target Optical, we help people see and look great - and Read more
Medical Assistant - Orthopedics *Apple* Hil...
Medical Assistant - Orthopedics Apple Hill York Location: WellSpan Medical Group, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Apply Now Read more
*Apple* Systems Administrator - JAMF - Activ...
…**Public Trust/Other Required:** None **Job Family:** Systems Administration **Skills:** Apple Platforms,Computer Servers,Jamf Pro **Experience:** 3 + years of Read more
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
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.