TweetFollow Us on Twitter

C++ and Toolbox
Volume Number:10
Issue Number:2
Column Tag:Think Top 10

Related Info: Quickdraw

Mixing C++ and the Toolbox

By Christopher Prinos, Symantec Technical Support, Symantec Corp.

Note: Source code files accompanying article are located on MacTech CD-ROM or source code disks.

This is a monthly column written by Symantec’s Technical Support Engineers intended to provide you with information on Symantec products. Each month we cover either a specific application of tools or a “Q&A” list.

Mixing C++ and the Toolbox

As the use of C++ continues to grow in the Macintosh community, programmers are faced with the challenge of learning ways to bring the added capabilities of the C++ language to their Macintosh development projects. A large number of people that contact us everyday are just getting their hands wet in the Toolbox, even more are just starting to learn C++. Bringing the two together is commonly described in the context of class libraries like the THINK Class Library and MacApp, and although these libraries provide powerful vehicles to develop object-oriented Macintosh applications, they may be a bit overwhelming at first. Those coming up to speed in C++ are often looking a for nudge in the right direction to start their Mac specific projects, without having to assimilate a large class library with its possibly subtle interaction between a large number of classes.

This article will take a more straightforward approach. We will build a C++ class using features like operator overloading, nested classes, and reference counting to implement a wrapper for QuickDraw regions that allows us to use them in a more intuitive and elegant manner. The final result will be a stand-alone class that doesn’t rely on a supporting cast of other classes to be effective, showing a concrete example of integrating some of the power of the C++ language with the Macintosh Toolbox.

What you should know

This article assumes that you have a basic understanding of QuickDraw in general, and QuickDraw regions in particular. You should be familiar with the way regions are created, and the basic operations that can be performed on them. If you need a bit of a refresher, look to Inside Macintosh Vol. 1, Chap 6., or the New Inside Macintosh: Overview, Chap. 5. You should also have a familiarity with C++ concepts such as class declarations, constructors and destructors, and overloaded operators. In general, I’ll assume the reader has done some Macintosh programming in another language (such as C or Pascal), but is getting started in C++.

Software Requirements

The code for this was written and tested using Symantec C++ for Macintosh, version 6.0.1. It will not compile using THINK C with object extensions. The code is also suitable for the MPW version of Symantec C++ without modification.

What’s the idea?

A major advantage of C++ (and object programming in general) is that it provides language supported constructs for the programmer to bind data (structures) and the operations (functions) that operate on that data. Using other features like inheritance and polymorphism allows objects to be extended and combined in powerful ways, but it is the underlying data-operation binding that enables objects to be used as “black boxes” in a high level, reusable way.

Defining a data structure and the operations that can be performed on it is often referred to as an Abstract Data Type (ADT). A common example is that of a stack. The data are the items in the stack, and the operations could be push, pop, and empty. How the data and operations are actually implemented can vary, but the concept is that of a set of data, and a set of actions. In a procedural language, that set of data and actions remains separated, but in C++ (and other object oriented languages), they are part of the same object.

The QuickDraw Region as an Abstract Data Type

The QuickDraw region is an excellent example of a procedural implementation of an ADT. There is a single data structure, a Region which is passed around from function to function using a handle to the structure, typedef’ed as a RgnHandle. Regions can be used to describe arbitrarily complex graphic outlines and areas, although they often describe simple rectangles. QuickDraw provides operations such as UnionRgn and XorRgn to manipulate the data in a region. The functions available are certainly complete, but can be unwieldy. Consider the following (contrived) code:


/* 1 */
RgnHandle a,b,c;
Rect    rect1;
PicHandle picH;
// ... assume above variables are defined here

RgnHandle d,e,f;
d = NewRgn();
e = NewRgn();

DiffRgn(b,c,d);  
UnionRgn(a,c,e);
if ( EmptyRgn(d) || EqualRgn(e,b) )
{
 OpenRgn();
 DrawPicture(picH, **picH.picFrame);
 CloseRgn(f);
 SetRectRgn(d, rect1.left, rect1.top, 
 rect1.right, rect1.bottom);
 UnionRgn(e,d,f);
} 
else
{
 f = NewRgn();
 SectRgn(d,e,f);
 SectRgn(f,b,f);
 CopyRgn(f,a);
}

We’d like to turn the code above into something like this:

/* 2 */

RgnHandle a,b,c;
Rect    rect1;
PicHandle picH;
// ... assume above variables are defined here

TRegion   d,e;

d = b-c;// difference
e = a+c;// union
if (!d || e==b)  // check for empty region, equality comparison
{
 TRegion f(picH);// construct TRegion from PicHandle
 f = e + rect1;  // combine Rect in expression with TRegion
}
else
{
 TRegion f;
 f = d & e & b;  // chain operators in expression
 a = f; // assignment
}

The biggest change to the code is the cleaner form granted by use of the overloaded operators. Here, we can use TRegions (the name of our region class) as we would a built-in type. Since a TRegion is a graphic object, we can use operators as we would to describe set notation in a Venn diagram. We can add regions, take their intersections, check for equality, and make direct assignments without function calls. In addition, we can create TRegions from a PicHandle, and use variables of type Rect in mixed expressions (the Rect is converted to a TRegion consisting of only that rectangle).

With this glimpse of how the final TRegion will be used, let’s take a look at how we form the class, starting with the class declaration. As a note, the complete TRegion code is at the end of this article--the code in the text consist mostly of fragments to show concepts described in the text.

Creating the TRegion class

The first thing to do is define the interface for our TRegion. first attempt might be something like this:

/* 3 */

class TRegion
{
 private:
 RgnHandlefRgn;
 public:
 TRegion()  { fRgn = NewRgn(); }
 ~TRegion() { DisposeRgn(fRgn); }

 // overloaded operators and other member functions...   
 ...  
 ...
};

In the declaration above, we have just one data member, the private RgnHandle fRgn. The default constructor initializes the TRegion by calling NewRgn(), similarly the destructor frees the allocated handle with DisposeRgn(). This is a reasonable model, since the RgnHandle is really the only thing we need to keep track of when using the Toolbox calls to manipulate a region. Later on, we’ll be modifying the internal structure a bit to improve performance, but for right now we’ll work with this declaration.

With the data of the class defined, as well as a simple constructor/destructor, we can move on to the operations that we’d like to implement. Initially, we’re interested in functions that could be directly mapped to operators. Taking a minute to inventory the QuickDraw functions that operate on regions, we derive the following chart showing the corresponding operator that will be overridden:

Function Operation Operator Example

overload

CopyRgn Copy/assign = a = b

DiffRgn Take the difference - a - b

of two regions

SectRgn Take the intersection & a & b

of two regions

UnionRgn Take the union | or + a | b, or a + b

of two regions

XorRgn Take the Exclusive-OR ^ a ^ b

of two regions

EqualRgn Compare two == a == b

regions for equality

From this basic set of operators, we can naturally add +=, -=, &=, |=, and ^= as convenient extensions that any user of the class would expect to have available. In addition we provide these additional operators for Boolean comparison (other than equality):

Boolean operator Operation Example

&& TRUE if both regions are if (a && b)

non empty, else FALSE

|| TRUE if either region is if (a || b)

non-empty, else FALSE

! TRUE if region is empty if (!a)

int() this is for A TRegion is TRUE if (a)

conversion in when evaluated in

Boolean a Boolean expression

expressions if it is non-empty

The use of these operators is fairly straightforward, so let’s look at their implementation. In all, we have decided to provide 16 operators, which we can be separated into two classifications: member and non-member freind operators

Member vs. non-member friend operators

At first, it might be tempting to implement all the overloaded operators as TRegion member functions, but there is an important reason not to do so. To understand why, let’s look at the difference. Here is the class declaration for operator+ and operator-, with the latter declared as a non-member:


/* 4 */

class TRegion
{
 // ... 
 public:
 TRegion& operator+(const TRegion&);

 friend TRegion& operator-(const TRegion&, const TRegion&);
 // ...
};
and the implementation:

TRegion& TRegion::operator+(const TRegion& r)
{
 TRegion temp;
 UnionRgn(fRgn, r.fRgn, temp.fRgn);
 return temp;
}

TRegion& TRegion::operator-(const TRegion& a, 
 const TRegion& b)
{
 TRegion temp;
 DiffRgn(a.fRgn, b.fRgn, temp.fRgn);
 return temp;
}

The operator- must be declared as a friend in the class declaration so that it can access the private data of TRegion objects. operator+ is a member function, so that it has no such requirement. In addition, operator+ needs only one argument, corresponding to the right operand in an expression. The left operand is the object itself. That is, the expression a+b is implemented as a.operator+(b), and all reference to a are through the implicit this pointer. In operator-, however, we specify both left and right hand side operands, so that a-b is like calling operator-(a,b).

So why use the non-member form? As it turns out, we will eventually want provide conversion operators for the TRegion class, as we saw in our first code example. In that example, we mixed a Rect in an expression with a TRegion. There is a Rect to TRegion conversion (not yet described) that gets applied anywhere a Rect is used in a TRegion expression. That is, for a TRegion a, and a Rect b, a+b is a legal expression.

The problem with a member operator overload is that it will not allow conversions to the left operand. So for that same TRegion a and Rect b, the expression b + a is not allowed, even though a + b is. By using a non-member overload, we can describe an operator that takes two TRegion operands. If any conversions need to be applied, they will be. With the non-member operator a - b, and b - a are both legal expressions.

The bottom line is that we want use non-member operators for those operators where a <op> b and b <op> a should both be allowed. This is true for the following operators: +, -, ^, &, |, &&, ||, and ==.

For assignment operators, the left hand side will always be a TRegion, since it is unclear what should happen if for TRegion a and Rect b we have an expression like b = a, or b+=a. In those cases, let the object on the left hand side of the expression worry about what to do. The following operators, then, are member operators: +=, -=, ^=, &=, and |=.

In general, you’ll want to define unary operators as member functions as well, since the single operand should be a TRegion, not an object of another type converted to a TRegion. Our two unary operators, operator! and operator int() are member functions for this reason.

Now that we have determined which operators are member vs. non member functions, let’s look at the new class declaration:


/* 5 */

class TRegion
{
 //...
 public 
 // member function operator overloads
 TRegion& operator=(const TRegion &r); // assignment
 
 operator int()  // Boolean conversion
  { return !EmptyRgn(fRgn); } // true if nonempty
   
 TRegion& operator+=(const TRegion &r);
 TRegion& operator-=(const TRegion &r);
 TRegion& operator^=(const TRegion &r);
 TRegion& operator|=(const TRegion &r);
 TRegion& operator&=(const TRegion &r);
 Booleanoperator!()
 {  return EmptyRgn(fRgn); }// true if empty 
 
 // non-member operator overloads
 friend TRegion operator+(const TRegion &a, 
 const TRegion &b);
 friend TRegion operator-(const TRegion &a, 
 const TRegion &b); 
 friend TRegion operator&(const TRegion &a, 
 const TRegion &b);
 friend TRegion operator|(const TRegion &a, 
 const TRegion &b);
 friend TRegion operator^(const TRegion &a, 
 const TRegion &b);
 friend Boolean operator==(const TRegion &a, 
 const TRegion &b);
 friend Boolean operator&&(const TRegion &a, 
 const TRegion &b);
 friend Boolean operator||(const TRegion &a, 
 const TRegion &b);
 // ...
}; 

To get a feel for the implementation, we’ll look at the function definitions for some of the operators. The conversion to int and operator! are already shown as inline definitions.

The assignment operator= is an overload that provides the copy operation for our class. It can be written as:

/* 6 */

TRegion& TRegion::operator=(const TRegion &r)
{
 CopyRgn(r.fRgn, fRgn); // *this = r
 return *this;
}

As will be the case with all the operators, the argument is a TRegion reference. This allows TRegions to be used “by value” without incurring the penalty of a copy operation when passed as argument[s] to the operator. The return value for member operators is also a reference--this time to the object itself. All member operators returning a TRegion& do so by returning *this.

The code for the operator itself couldn’t be simpler, it merely does a CopyRgn() from the TRegion on the right of the = to the this object. Because we return a TRegion&, we can also chain this operator, as in a = b = c = d.

operator+= is another straightforward implementation, this time using a call to UnionRgn to perform the “addition”:

/* 7 */

TRegion& TRegion::operator+=(const TRegion &r)
{
 UnionRgn(fRgn, r,fRgn, fRgn);// *this = *this + r
 return *this;
}

All the remaining member operators are implemented in a similar fashion, with only the Toolbox function changing as appropriate. Here is a non member implementation of operator^:

/* 8 */

TRegion TRegion::operator^(const TRegion& a, const TRegion& b)
{
 TRegion tmpRgn;
 XorRgn(a.fRgn, b.fRgn, tmpRgn.fRgn);// a ^ b
 return tmpRgn;
}

Notice that the return type this time is a TRegion, rather than a TRegion&. Instead, we need to form a temporary on the stack to hold the result of the operation, and return that result. We can’t return a reference to the temporary since it is deallocated at the end of the function.

This has an important performance implication. For all our non-member operators, we have to form a temporary TRegion to hold the result, whereas our member operators operate directly on the *this object. Because of the extra allocation, then, an expression like

/* 9 */

a += b;
is more efficient than

a = a+b;

even though both are semantically equivalent. Note that there is no way for the compiler to optimize your use of a=a+b into a+=b like it can with built-in types. The reason is that a=a+b could be semantically defined to be different than a+=b, though this would certainly be confusing, and poor interface design.

The rest of the non-member operators have similar implementations, again with only the adjustment for the necessary Toolbox call.

Better constructors

Our original declaration for TRegion specified a simple default constructor. But a user may want to create a region several different ways, and we should oblige this with a better set of constructors.

The first new constructor that comes to mind is by specifying a RgnHandle directly, so that you could create a new TRegion given any existing RgnHandle. But there are several other ways that we might like to specify a TRegion creation... here’s what we’ll implement:

Create a TRegion from: Constructor declaration

<default constructor> TRegion();

RgnHandle TRegion(const RgnHandle r);

Rect TRegion(const Rect &r);

another TRegion TRegion(const TRegion& r);

(copy constructor)

four shorts TRegion(short l, short t, short r,

(left, top, right, bottom) short b);

PolyHandle TRegion(const PolyHandle p);

(TRegion from polygon)

PicHandle TRegion(const PicHandle p);

(TRegion from a Mac picture)

Now there are several ways to create a TRegion, instead of just one. Let’s look at the implementation for the Rect constructor:

/* 10 */

TRegion::TRegion(const Rect &r)
{
 fRgn = NewRgn();
 RectRgn(fRgn, &r);
}

Again we get to use a Toolbox call to do the dirty work. Creating a TRegion from a polygon is a bit more interesting:


/* 11 */

TRegion::TRegion(const PolyHandle p)
{
 Rect destRect;
 
 destRect = (*p)->polyBBox;
 OpenRgn();
 FramePoly( p );
 CloseRgn(fRgn);
}

Here we use the passed PolyHandle to render a framed polygon, and use that as the basis for our region. The PicHandle constructor uses a similar scheme.

The best part about our new constructors is that they do double duty as conversion operators. In other words, now that we have constructors capable of making TRegions from PolyHandles, PicHandles, Rects, etc., a conversion of those types will be applied whenever necessary. This primarily affects our operator overloads, and allows us to do things like this:


/* 12 */

// assume PicHandle pic, Rect r, PolyHandle poly, TRegion rgnA
TRegion myRgn = (pic + r) ^ (poly & rgnA);

A lot of work is being done behind the scenes here; TRegions are being created from the PicHandle, Rect, and PolyHandle, as well as the temporary TRegions for the intermediate expressions. Nonetheless, the combination of operator overloading and implicit conversion operator let’s us write a single seamless expression in a very intuitive way.

Other conversion and utility member functions

With a core set of constructors/conversion operators, and operator overloads, our TRegion class is starting to fill out nicely. The only thing left is for a couple of utility functions to be added. Once again, we’re going to build on the work Apple has done for us. We’re going to define the following utility functions (shown with their corresponding Toolbox call):

Member function Corresponding

declaration Toolbox call

void Empty() SetEmptyRgn

Boolean IsEmpty() const EmptyRgn

void Offset(short h, short v) OffsetRgn

void Offset(Point p) OffsetRgn

void Inset(short h, short v) InsetRgn

void Inset(Point p) InsetRgn

Boolean ContainsRect(const Rect& r) const RectInRgn

Boolean ContainsPoint(Point p) const PointInRgn

There are a few points of interest here. I’ve changed the function to query for an empty region from EmptyRgn to IsEmpty and the setting of a region to empty from SetEmptyRgn to Empty as a matter of personal taste. The latter are more clear to me, feel free to change them. Two versions of Inset and Offset are provided so that you don’t have to muddle your code with Point conversions.

Also note that the IsEmpty, ContainsRect, and ContainsPoint are const member functions. They are declared this way because they don’t modify any part of a TRegion, so that they can be called for a const TRegion. If they weren’t declared const, code like the following wouldn’t compile:

const TRegion r;
Boolean b;
...
b = r.IsEmpty();

The implementation of all these functions is very straightforward, so I won’t describe it here. Check the listings at the end of this article for the full source.

One last function that we want to add to our TRegion class is another conversion operator. This time, instead of converting from an object to a TRegion, we’ll be converting from a TRegion. When is this useful? When we have a TRegion, but need a RgnHandle. This could be the case in existing code or libraries, or with other Toolbox calls. We want to be able to use our spiffy new TRegion anywhere a RgnHandle is called for, so we can have code like this:

TRegion myTRgn
//...
CopyBits(&srcMap, &destMap, &srcRect, &destRect, 
 tMode, myTRgn);

The declaration and inline definition for this type converter is

operator RgnHandle() { return fRgn; }

Now that a TRegion can look like a RgnHandle, there shouldn’t be any place we can’t use the TRegion.

Let’s look at the TRegion header with all of our additions so far:

/* 13 */

class TRegion
{
 public:
 // constructors (and implied conversion operators)
 TRegion();
 TRegion(const RgnHandle r);
 TRegion(const Rect &r);
 TRegion(const TRegion &r);
 TRegion(short left, short top, short right, short bottom);
 TRegion(const PolyHandle p);
 TRegion(const PicHandle p);
 
 // destructors
 ~TRegion();
 
 // Utility member functions
 void   Empty();
 Boolean  IsEmpty() const;
 void   Offset(short h, short v);
 void   Offset(Point p);
 void   Inset(short h, short v);
 void   Inset(Point p);
 BooleanContainsRect(const Rect &r) const;
 Boolean  ContainsPoint(Point p) const;
 
 // member function operator overloads
 TRegion& operator=(const TRegion &r); // assignment
 
    operator RgnHandle()  // conversion to RgnHandle
   { return fRgn; }
  operator int() // Boolean conversion
   { return !EmptyRgn(fRgn); }// true if nonempty
   
 TRegion& operator+=(const TRegion &r);
 TRegion& operator-=(const TRegion &r);
 TRegion& operator^=(const TRegion &r);
 TRegion& operator|=(const TRegion &r);
 TRegion& operator&=(const TRegion &r);
 Booleanoperator!()
   { return EmptyRgn(fRgn); } 
 
// non-member operator overloads
// these operators need to be friends since they access 
// private members
// they are defined as non-member functions for mixed 
// type expressions
 friend TRegion operator+(const TRegion &a, 
 const TRegion &b);
 friend TRegion operator-(const TRegion &a, 
 const TRegion &b); 
 friend TRegion operator&(const TRegion &a, 
 const TRegion &b);
 friend TRegion operator|(const TRegion &a, 
 const TRegion &b);
 friend TRegion operator^(const TRegion &a, 
 const TRegion &b);
 friend Boolean operator==(const TRegion &a, 
 const TRegion &b);
 friend Boolean operator&&(const TRegion &a, 
 const TRegion &b);
 friend Boolean operator||(const TRegion &a, 
 const TRegion &b);
 
 private:
 RgnHandlefRgn;
};

Learning to count

At this point, the TRegion class is functionally complete, and its interface is pretty much set. Still, there’s a bit more we can do to improve the performance of the TRegion class, without changing the flexibility of the interface that we’ve worked to achieve so far.

The last modification to TRegion will implement a technique called reference counting that makes copying and assignment more efficient. The idea is that whenever more than one TRegion has the same data (that is to say, they describe identical regions), we want only one copy of the data, with all the TRegions pointing to it. Currently, if we write code like the following:

TRegion a,b,c;
//...
a = b = c;

then CopyRgn is called once for each assignment; once to copy c to b, and once for b to a. Not only that, but we now have three identical regions in our heap. If these particular regions are complex, then we are wasting memory in addition to wasting time in CopyRgn. Here’s a picture after the copy:

With reference counting, we can eliminate this overhead by treating the TRegion data as a separate object from the TRegion itself. In addition, we will keep a reference count for each data object so that at any given time we know how many TRegions are using it. Now when we make the assignment a=b=c, only the data pointers for each TRegion are adjusted; CopyRgn is never called, and there will be only one region sitting in the heap. Using reference counting, the post assignment objects would look like this:

If we modify any of the three TRegions at this point, we have to create a new data object, and detach the TRegion from the old data. For example, given the a=b=c assignment, suppose we now write:

c = c.Inset(10,10) + r;

where r is some existing Rect.

TRegion c no longer can use the same data, so we create a new data object and adjust the pointer. Going to our picture again, things would look like this after the Inset operation:

Now that we know what reference counting is for, let’s look at the implementation.

Creating a separate data class

The first step is to create a new class that contains region data, and a count of the references to that object. Instead of containing the region data (a RgnHandle), TRegions will now only contain a pointer to the new data class. The new class will be called RegionData, and it looks like this:

/* 14 */

struct RegionData
{
 RgnHandlefRgnH; // Mac Region handle
 short  fCount;  // reference count
 
 RegionData()    { fCount = 1; fRgnH = NewRgn(); }
 RegionData(RgnHandle r)  { fCount = 1; fRgnH = r; }
 ~RegionData()   { DisposeRgn (fRgnH); }
}; 

Here we’ve included all the constructors and destructors that we’ll need. Note that the job of allocating and deallocating RgnHandles now rests solely on the RegionData class. The only operation on fCount, the reference counter, is to initialize it to 1 when a RegionData object is created. Changing the reference count will be a job for TRegion.

RegionData objects are really of importance only to TRegions, so we’ll make RegionData a nested class of TRegion. In addition, we still need to change the data member of TRegion from a RgnHandle to a RegionData pointer. The private section of TRegion now looks like this:

/* 15 */

class TRegion
{
 public:
 // ...
 private:
 struct RegionData
 { 
 // as described above
 };
 RegionData *fRgn// data member now  a RegionData *
};

Changing the fRgn member to a RegionData * changes the way we have to write TRegion member and non-member friend functions. Here’s how some of the constructors change:

TRegion::TRegion()
{
 fRgn = new RegionData;
}

TRegion::TRegion(const Rect &r)
{
 fRgn = new RegionData;
 RectRgn(fRgn->fRgnH, &r);
}

The default constructor now creates a RegionData object instead of calling NewRgn. A similar operation is done for the Rect constructor. Notice how it accesses the RgnHandle fRgnH from the RegionData object.

The copy constructor shows off the reference counting:

TRegion::TRegion(const TRegion &r)
{
 fRgn = r.fRgn;
 fRgn->fCount++;
}

Notice that CopyRgn is no longer used. All we are doing is making fRgn point to the same RegionData object as r.fRgn. In addition, we increment the reference count.

Evidence of the reference counting scheme can be seen in the destructor as well:

TRegion::~TRegion()
{
 if ( --fRgn->fCount <= 0 )
 delete fRgn;
}

Here the destructor checks the reference count of the data object before deleting it. If more than one TRegion are using this data, we don’t want to deallocate it, only decrement the reference count. If the count drops below 1, though, we can go ahead and free the memory.

With reference counting, assignment now becomes a matter of assigning pointers, rather than copying regions:

TRegion& TRegion::operator=(const TRegion &r)   // assignment 
{
 r.fRgn->fCount++; 
 if (--fRgn->fCount == 0)
 delete fRgn;
 fRgn = r.fRgn;
 return *this;   // *this = r;
}

The first thing the operator= does is increment the reference count of the object on the right-hand side. The if statement then checks to see if the TRegion on the left has more than one reference. If it doesn’t, we can dispose of the data that it used to refer to. If we forget this step, we could end up with a RegionData object sitting in the heap, with no way to free it.

Changing the rest of our functions is a fairly straightforward affair. For the non-member friend operators the changes are trivial: we only need to change fRgn to fRgn->fRgnH in most cases. Here’s an example with operator&:

TRegion operator&(const TRegion &a, const TRegion &b)    
// intersection
{
 TRegion tempRgn;
 SectRgn( a.fRgn->fRgnH, b.fRgn->fRgnH, 
 tempRgn.fRgn->fRgnH );   // a & b;
 return tempRgn;
}

The other non-member friend operators get similar changes.

The member operators require a bit more added code. Since they modify an object which may have a reference count > 1, they need to check for this possibility, and create a new RegionData object if necessary. If the reference count of the this object is 1, it is OK to go ahead and modify the region. Here’s a look at the code for operator-= :

TRegion& TRegion::operator-=(const TRegion &r)     
{
 if (fRgn->fCount > 1)
 {
 RegionData *tmpRgn = fRgn;
 --fRgn->fCount;
 fRgn = new RegionData;
 CopyRgn(tmpRgn->fRgnH, fRgn->fRgnH);
 }
 DiffRgn( fRgn->fRgnH, r.fRgn->fRgnH, fRgn->fRgnH );
 return *this;
}

The major addition here is the if statement at the top. If we do need to detach from a shared RegionData object, we need to create a new one and copy the data. We do the if (fRgn-fCount > 1) check so that we don’t create copy overhead for TRegions that aren’t sharing their RegionData object with other TRegions. This conditional test ends up being repeated for alot of the member functions, so we’ll make it it’s own private member function called DetachSharedRgn:

void TRegion::DetachSharedRgn(void)
{
    if (fRgn->fCount > 1)
    {
        RegionData *tmpRgn = fRgn;
        --fRgn->fCount;
        fRgn = new RegionData;
        CopyRgn(tmpRgn->fRgnH, fRgn->fRgnH);
    }
}

With this change, operator-= now looks like:

TRegion& TRegion::operator-=(const TRegion &r)  
{
    DetachSharedRgn();
    DiffRgn(fRgn->fRgnH, r.fRgn->fRgnH, fRgn->fRgnH);
    return *this;
}

As a rule, any member function that intends to modify a TRegion’s RegionData object should call DetachSharedRgn first.

The other member operators get similar changes. You can see the final results in the code listing below.

Where to go from here

We’ve gone through an extended example of mixing C++ with the Toolbox, resulting in a TRegion class that offers several advantages over plain old Regions and RgnHandles, yet is usable in existing code. What we really did was take existing Toolbox functions and provide a high level wrapper for the data that they operate on. You can easily extend this idea to work with other elements of the Toolbox.

TRegion can still be improved in several ways. There are always new constructors and member functions to add. Many of the existing member functions can be converted to inline functions to reduce function call overhead. Also, one could write custom memory allocation functions (operator new and delete) for the TRegion and RegionData classes. As it stands now, a TRegion is only 4 bytes, and a RegionData is 6, so allocating those small sizes with the standard memory allocation routines is a bit of a waste.

Also, the existing TRegion doesn’t really do anything in the way of error checking to see if memory allocations fail. One way to address this issue would be use set_new_handler() to set up a callback function in the case of an allocation failure when using operator new. See section 12.5 in either the Annotated C++ Reference Manual (Bjarne Stroustrup, Margaret A. Ellis), or The C++ Programming Language (Stroustrup) for more information on how to do this.

The goal though, was to get you started in the right direction for merging new-found C++ knowledge with the tried-and-true procedural Toolbox. Where you take it from here is up to you.

TRegion Source Code

/* 16 */

// ** TRegion.h **
//
// TRegion - class wrapper for Quickdraw regions
//
// Chris Prinos ©1993, Symantec Corp
//
// TRegion is a class wrapper for the Quickdraw Region 
// type that provides overloaded   operators to simplify 
// expressions with regions and other Quickdraw entities used 
// to form regions. 
//
// TRegions can be used in mixed expressions such as  
// x = a <op> b, where:
// 
// • x is a TRegion
// 
// • a and b are one of:  - TRegion
// - RgnHandle
// - PicHandle
// - Rect
// - PolyHandle
// 
// • <op> is one of: +   union of a and b
// -   difference between a and b (b from a)
// ^   XOR of a and b
// &   intersection of a and b
// |   union of a and b (same as + )
//
// • operators can be chained, like x = a ^ b | (c-(d & f));
//
// • operators +=, -=, ^=, |=, &= are also provided and 
// are more efficient than their expanded versions, 
// i.e., use a += c instead of a = a + c for 
// best performance
//
// • Boolean operators ||, &&, ==, and ! are provided. 
// If a TRegion is evaluated in the context of a boolean 
// expression, it is true if the region is nonempty. 
// a == b is true if and b describe the same region
//
//  TRegions can be constructed in a variety of ways. In all 
//   cases, the constructor takes care of allocating space for 
//  the private RegionData.  The destructor disposes of any 
//  allocated memory for a region. The constructors 
//  can be specified by any of the following:
//
// • default- creates a nil region
// • a RgnHandle - makes a TRegion with the 
// RgnHandle (doesn’t copy)
// • a PicHandle - makes a TRegion that surrounds a 
// Macintosh picture
// • a Rect - makes a TRegion that surround the Rect
// • a PolyHandle- makes a TRegion that surrounds 
// the Polygon
// • TRegion- makes a TRegion with another TRegion 
// (copy ctor)
//
//  Conversion operators: TRegion defines several conversion 
//  operators. Constructors provide conversions to the type 
//  TRegion from the following: RgnHandle, PicHandle, Rect, 
//  PolyHandle. In addition, a conversion operator is provided 
//  to convert from TRegion to RgnHandle so that TRegions can 
//  be used anywhere a RgnHandle is called for, and from 
//  TRegion to int so that TRegions can appear in boolean 
//  expressions
//
// Utility functions. The following utility functions 
// are provided for TRegion:
//
// • Empty()- will empty the TRegion
// • IsEmpty()   - returns true if the specified 
// TRegion is true
// • Offset()    - same as OffsetRgn(), but it can take 
// a Point parameter
// • Inset()- same as InsetRgn(), but it can take 
// a Point parameter 
// • ContainsRect- same as RectInRgn()
// • ContainsPoint - same as PtInRgn()
//
// TRegion uses a nested RegionData class to facilitate 
// reference counting. The reference counting eliminates copy 
// and assignment overhead, as well as reducing storage 
// requirements

#pragma once

class TRegion
{
 public:
 // constructors (and implied conversion operators)
 TRegion();
 TRegion(const RgnHandle r);// doesn't copy data
 TRegion(const Rect &r);
 TRegion(const TRegion &r);
 TRegion(short left, short top, short right, short bottom);
 TRegion(const PolyHandle p);
 TRegion(const PicHandle p);
 
 // destructors
 ~TRegion();
 
 // Utility member functions
 void   Empty();
 Boolean  IsEmpty() const;
 void   Offset(short h, short v);
 void   Offset(Point p);
 void   Inset(short h, short v);
 void   Inset(Point p);
 BooleanContainsRect(const Rect &r) const;
 Boolean  ContainsPoint(Point p) const;
 
 // member function operator overloads
 TRegion& operator=(const TRegion &r); // assignment
 
    operator RgnHandle()  // conversion to RgnHandle
   { return fRgn->fRgnH; }
    operator int()    // Boolean conversion
   { return !EmptyRgn(fRgn->fRgnH); } // true if nonempty
   
 TRegion& operator+=(const TRegion &r);
 TRegion& operator-=(const TRegion &r);
 TRegion& operator^=(const TRegion &r);
 TRegion& operator|=(const TRegion &r);
 TRegion& operator&=(const TRegion &r);
 Booleanoperator!()
   { return EmptyRgn(fRgn->fRgnH); } 
 
 // non-member operator overloads
 // these operators need to be friends since they access 
 // private members they are defined as non-member functions 
 // for mixed type expressions
 friend TRegion operator+(const TRegion &a, 
 const TRegion &b);
 friend TRegion operator-(const TRegion &a, 
 const TRegion &b); 
 friend TRegion operator&(const TRegion &a, 
 const TRegion &b);
 friend TRegion operator|(const TRegion &a, 
 const TRegion &b);
 friend TRegion operator^(const TRegion &a, 
 const TRegion &b);
 friend Boolean operator==(const TRegion &a, 
 const TRegion &b);
 friend Boolean operator&&(const TRegion &a, 
 const TRegion &b);
 friend Boolean operator||(const TRegion &a, 
 const TRegion &b);
 
 private:
 struct RegionData // nested data class
 {
 RgnHandlefRgnH; // Mac Region handle
 short  fCount;  // reference count
 
 RegionData()    { fCount = 1; fRgnH = NewRgn(); }
 RegionData(RgnHandle r)  { fCount = 1; fRgnH = r; }
 ~RegionData()   { DisposeRgn (fRgnH); }
 };
 RegionData *fRgn;

 // to disconnect from shared RegionData object
 void   DetachSharedRgn();
};


// ** TRegion.cp **
//
// TRegion - a class wrapper for Quickdraw regions
//
// Chris Prinos ©1993, Symantec Corp.

#include "TRegion.h"

TRegion::TRegion() // default constructor
{
 fRgn = new RegionData;
}

TRegion::~TRegion()// delete if data not referenced by
{// more than one TRegion
 if ( --fRgn->fCount <= 0 )
 delete fRgn;
}

TRegion::TRegion(const Rect &r)  // build a TRegion from a Rect
{
 fRgn = new RegionData;
 RectRgn(fRgn->fRgnH, &r);
}

TRegion::TRegion(const TRegion &r) // copy constructor
{
 fRgn = r.fRgn;  // adjust pointers, increment 
 fRgn->fCount++; // reference count
}

TRegion::TRegion(const RgnHandle r)
// create TRegion from RgnHandle
{
 // assumes r is an allocated Quickdraw region
 fRgn = new RegionData(r);  
}

TRegion::TRegion(short left, short top, 
 short right, short bottom)
{
 fRgn = new RegionData;   
 SetRectRgn(fRgn->fRgnH, left, top, right, bottom);
}

TRegion::TRegion(const PicHandle p)
// build a TRegion from a PicHandle
{
    Rect  destRect;  
    RgnHandle    tmpRgn;

    destRect = (*p)->picFrame; // use current picture location
    OpenRgn();
        DrawPicture( p, &destRect );
    CloseRgn(tmpRgn);   // OpenRgn/CloseRgn allocates region
   // pass the allocated region to RegionData constructor
    fRgn = new RegionData(tmpRgn);
}

TRegion::TRegion(const PolyHandle p)
// build a TRegion from a PolyHandle
{
    Rect  destRect;
    RgnHandle    tmpRgn;
    
    destRect = (*p)->polyBBox;  // use current polygon's frame
    OpenRgn();
        FramePoly( p );
    CloseRgn(tmpRgn);               
    fRgn = new RegionData(tmpRgn);
    
}

void TRegion::Empty()// clear a region
{
 DetachSharedRgn();
 SetEmptyRgn(fRgn->fRgnH);
}

Boolean TRegion::IsEmpty() const // TRUE if empty region
{
 return EmptyRgn(fRgn->fRgnH);
}

void TRegion::Offset(short h, short v) // like OffsetRgn
{
 if (h != 0 && v != 0)    // don't bother if offsets are zero
 {
 DetachSharedRgn();
 OffsetRgn(fRgn->fRgnH, h, v);
 }
};

void TRegion::Offset(Point p)  // like Offset
{
 if (p.h !=0 && p.v != 0) // don't bother if point is (0,0)
 {
 DetachSharedRgn();
 OffsetRgn(fRgn->fRgnH, p.h, p.v);
 }
}

void TRegion::Inset(short h, short v)// like InsetRgn
{
 if (h != 0 && v != 0)    // don't bother if insets are zero
 {
 DetachSharedRgn();
 InsetRgn(fRgn->fRgnH, h, v);
 }
}

void TRegion::Inset(Point p)// like InsetRgn
{
 if (p.h !=0 && p.v !=0)  // don't bother if point is (0,0)
 {
 DetachSharedRgn();
 InsetRgn(fRgn->fRgnH, p.h, p.v);
 }
}

Boolean TRegion::ContainsRect(const Rect &r) const
{
 return RectInRgn(&r, fRgn->fRgnH);
}

Boolean TRegion::ContainsPoint(Point p) const
{
 return PtInRgn(p, fRgn->fRgnH);
}

TRegion& TRegion::operator=(const TRegion &r) // assignment 
{
 r.fRgn->fCount++; 
 if (--fRgn->fCount == 0) // delete current region only if 
 delete fRgn;    // no other references
 fRgn = r.fRgn;
 return *this;   // *this = r;
}

TRegion& TRegion::operator+=(const TRegion &r)  
{
 DetachSharedRgn();
 // *this = *this + r
 UnionRgn(fRgn->fRgnH, r.fRgn->fRgnH, fRgn->fRgnH);      
 return *this;
}

TRegion& TRegion::operator-=(const TRegion &r)     
{
 DetachSharedRgn();
 // *this = *this - r
 DiffRgn(fRgn->fRgnH, r.fRgn->fRgnH, fRgn->fRgnH); 
 return *this;
}

TRegion& TRegion::operator^=(const TRegion &r)
{
 DetachSharedRgn();
 // *this = *this ^ r
 XorRgn(fRgn->fRgnH, r.fRgn->fRgnH, fRgn->fRgnH);  
 return *this;
}

TRegion& TRegion::operator|=(const TRegion &r)
{
 DetachSharedRgn();
 // *this = *this | r
 UnionRgn(fRgn->fRgnH, r.fRgn->fRgnH, fRgn->fRgnH);      
 return *this;
}

TRegion& TRegion::operator&=(const TRegion &r)
{
 DetachSharedRgn();
 // *this = *this & r
 SectRgn(fRgn->fRgnH, r.fRgn->fRgnH, fRgn->fRgnH); 
 return *this;
}

TRegion operator+(const TRegion &a, const TRegion &b) 
 // union
{
 TRegion tempRgn;
 //  a + b;
 UnionRgn(a.fRgn->fRgnH, b.fRgn->fRgnH, tempRgn.fRgn->fRgnH);  
 return tempRgn;
}

TRegion operator-(const TRegion &a, const TRegion &b)    
 // difference
{
 TRegion tempRgn;
 // a - b;
 DiffRgn(a.fRgn->fRgnH, b.fRgn->fRgnH, tempRgn.fRgn->fRgnH);   
 return tempRgn;
}
 
TRegion operator&(const TRegion &a, const TRegion &b)    
 // intersection
{
 TRegion tempRgn;
 // a & b;
 SectRgn(a.fRgn->fRgnH, b.fRgn->fRgnH, tempRgn.fRgn->fRgnH);   
 return tempRgn;
}

TRegion operator|(const TRegion &a, const TRegion &b)    
 // union, same as operator+
{
 TRegion tempRgn;
 // a | b;
 UnionRgn(a.fRgn->fRgnH, b.fRgn->fRgnH, tempRgn.fRgn->fRgnH);
 return tempRgn;
}

TRegion operator^(const TRegion &a, const TRegion &b) 
 // XOR
{
 TRegion tempRgn;
 // a ^ b;
 XorRgn(a.fRgn->fRgnH, b.fRgn->fRgnH, tempRgn.fRgn->fRgnH);
 
 return tempRgn;
}

Boolean operator==(const TRegion &a, const TRegion &b)
 // comparison
{
 if (a.fRgn == b.fRgn)    // compare the RegionData pointers. 
 // This is quicker than an EqualRgn() call, but isn't
 // conclusive if the pointers aren't the same
 return true;    
 else
 return EqualRgn(a.fRgn->fRgnH, b.fRgn->fRgnH);
}

Boolean operator&&(const TRegion &a, const TRegion &b)
{
 return // a && b true if both are non-empty
 ( !EmptyRgn(a.fRgn->fRgnH) && !EmptyRgn(b.fRgn->fRgnH) );
}

Boolean operator||(const TRegion &a, const TRegion &b)
{
 return // a && b true if both are non-empty
  ( !EmptyRgn(a.fRgn->fRgnH) || !EmptyRgn(b.fRgn->fRgnH) );
}

void TRegion::DetachSharedRgn(void)
{
    if (fRgn->fCount > 1) // uses a shared RegionData,
    { // create a copy, and disconnect from the 
 // shared RegionData object, otherwise, 
 // leave this TRegion's data alone.
        RegionData *tmpRgn = fRgn;
        --fRgn->fCount;
        fRgn = new RegionData;
        CopyRgn(tmpRgn->fRgnH, fRgn->fRgnH);
    }
}







  
 
AAPL
$116.47
Apple Inc.
+0.16
MSFT
$47.98
Microsoft Corpora
-0.72
GOOG
$537.50
Google Inc.
+2.67

MacTech Search:
Community Search:

Software Updates via MacUpdate

Cobook 3.0.7 - Intelligent address book....
Cobook Contacts is an intuitive, engaging address book. Solve the problem of contact management with Cobook Contacts and its simple interface and powerful syncing and integration possibilities.... Read more
StatsBar 1.9 - Monitor system processes...
StatsBar gives you a comprehensive and detailed analysis of the following areas of your Mac: CPU usage Memory usage Disk usage Network and bandwidth usage Battery power and health (MacBooks only)... Read more
Cyberduck 4.6 - FTP and SFTP browser. (F...
Cyberduck is a robust FTP/FTP-TLS/SFTP browser for the Mac whose lack of visual clutter and cleverly intuitive features make it easy to use. Support for external editors and system technologies such... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Evernote 6.0.1 - Create searchable notes...
Evernote allows you to easily capture information in any environment using whatever device or platform you find most convenient, and makes this information accessible and searchable at anytime, from... Read more
calibre 2.11 - Complete e-library manage...
Calibre is a complete e-book library manager. Organize your collection, convert your books to multiple formats, and sync with all of your devices. Let Calibre be your multi-tasking digital... Read more
Herald 5.0.1 - Notification plugin for M...
Note: Versions 2.1.3 (for OS X 10.7), 3.0.6 (for OS X 10.8), and 4.0.8 (for OS X 10.9) are no longer supported by the developer. Herald is a notification plugin for Mail.app, Apple's Mac OS X email... Read more
Firetask 3.7 - Innovative task managemen...
Firetask uniquely combines the advantages of classical priority-and-due-date-based task management with GTD. Stay focused and on top of your commitments - Firetask's "Today" view shows all relevant... Read more
TechTool Pro 7.0.6 - Hard drive and syst...
TechTool Pro is now 7, and this is the most advanced version of the acclaimed Macintosh troubleshooting utility created in its 20-year history. Micromat has redeveloped TechTool Pro 7 to be fully 64... Read more
PhotoDesk 3.0.1 - Instagram client for p...
PhotoDesk lets you view, like, comment, and download Instagram pictures/videos! (NO Uploads! / Image Posting! Instagram forbids that! AND you *need* an *existing* Instagram account). But you can do... Read more

Latest Forum Discussions

See All

Ubisoft Gives Everyone Two New Ways to E...
Ubisoft Gives Everyone Two New Ways to Earn In-Game Stuff for Far Cry 4 Posted by Jessica Fisher on November 21st, 2014 [ permalink ] | Read more »
Golfinity – Tips, Tricks, Strategies, an...
Dig this: Would you like to know what we thought of being an infinite golfer? Check out our Golfinity review! Golfinity offers unlimited ways to test your skills at golf. Here are a few ways to make sure your score doesn’t get too high and your... | Read more »
Dark Hearts, The Sequel to Haunting Meli...
Dark Hearts, The Sequel to Haunting Melissa, is Available Now Posted by Jessica Fisher on November 21st, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Meowza! Toyze Brings Talking Tom to Life...
Meowza! | Read more »
Square Enix Announces New Tactical RPG f...
Square Enix Announces New Tactical RPG for Mobile, Heavenstrike Rivals. Posted by Jessica Fisher on November 21st, 2014 [ permalink ] With their epic stories and gorgeous graphics, | Read more »
Quest for Revenge (Games)
Quest for Revenge 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: The great Kingdom of the west has fallen. The gods ignore the prayers of the desperate. A dark warlord has extinguished... | Read more »
Threadz is a New Writing Adventure for Y...
Threadz is a New Writing Adventure for You and Your Friends Posted by Jessica Fisher on November 21st, 2014 [ permalink ] In the tradition of round-robin storytelling, | Read more »
SteelSeries Stratus XL Hardware Review
Made by: SteelSeries Price: $59.99 Hardware/iOS Integration Rating: 4 out of 5 stars Usability Rating: 4.5 out of 5 stars Reuse Value Rating: 4.25 out of 5 stars Build Quality Rating: 4.5 out of 5 stars Overall Rating: 4.31 out of 5 stars | Read more »
ACDSee (Photography)
ACDSee 1.0.0 Device: iOS iPhone Category: Photography Price: $1.99, Version: 1.0.0 (iTunes) Description: Capture, perfect, and share your photos with ACDSee. The ACDSee iPhone app combines an innovative camera, a powerful photo... | Read more »
ProTube for YouTube (Entertainment)
ProTube for YouTube 2.0.2 Device: iOS Universal Category: Entertainment Price: $1.99, Version: 2.0.2 (iTunes) Description: ProTube is the ultimate, fully featured YouTube app. With it's highly polished design, ProTube offers ad-free... | Read more »

Price Scanner via MacPrices.net

15″ 2.2GHz Retina MacBook Pro on sale for $17...
 B&H Photo has the 2014 15″ 2.2GHz Retina MacBook Pro on sale today for $1749. Shipping is free, and B&H charges NY sales tax only. B&H will also include free copies of Parallels Desktop... Read more
27-inch 3.5GHz 5K iMac in stock today and on...
 B&H Photo has the new 27″ 3.5GHz 5K iMac in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP, and it’s the lowest price available... Read more
21-inch 1.4GHz iMac on sale for $979, save $1...
B&H Photo has the new 21″ 1.4GHz iMac on sale for $979.99 including free shipping plus NY sales tax only. Their price is $120 off MSRP. B&H will also include free copies of Parallels Desktop... Read more
13-inch 1.4GHz/256GB MacBook Air on sale for...
B&H Photo has lowered their price on the 13″ 1.4GHz/256GB MacBook Air to $1059.99 including free shipping plus NY sales tax only. Their price is $140 off MSRP, and it’s the lowest price for this... Read more
Save up to $400 with Apple refurbished 2014 1...
The Apple Store has restocked Apple Certified Refurbished 2014 15″ Retina MacBook Pros for up to $400 off the cost of new models. An Apple one-year warranty is included with each model, and shipping... Read more
New 13-inch 1.4GHz MacBook Air on sale for $8...
 Adorama has the 2014 13″ 1.4GHz/128GB MacBook Air on sale for $899.99 including free shipping plus NY & NJ tax only. Their price is $100 off MSRP. B&H Photo has the 13″ 1.4GHz/128GB MacBook... Read more
Apple Expected to Reverse Nine-Month Tablet S...
Apple and Samsung combined accounted for 62 percent of the nearly 36 million branded tablets shipped in 3Q 2014, according to early vendor shipment share estimates from market intelligence firm ABI... Read more
Stratos: 30 Percent of US Smartphone Owners t...
Stratos, Inc., creator of the Bluetooth Connected Card Platform, has announced results from its 2014 Holiday Mobile Payments Survey. The consumer survey found that nearly one out of three (30 percent... Read more
2014 1.4GHz Mac mini on sale for $449, save $...
 B&H Photo has lowered their price on the new 1.4GHz Mac mini to $449.99 including free shipping plus NY tax only. Their price is $50 off MSRP, and it’s the lowest price available for this new... Read more
Check Apple prices on any device with the iTr...
MacPrices is proud to offer readers a free iOS app (iPhones, iPads, & iPod touch) and Android app (Google Play and Amazon App Store) called iTracx, which allows you to glance at today’s lowest... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC)- Retail S...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Project Manager, *Apple* Financial Services...
**Job Summary** Apple Financial Services (AFS) offers consumers, businesses and educational institutions ways to finance Apple purchases. We work with national and Read more
*Apple* Store Leader Program - College Gradu...
Job Description: Job Summary As an Apple Store Leader Program agent, you can continue your education as you major in the art of leadership at the Apple Store. You'll Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.