- •Contents
- •Introduction
- •Who This Book Is For
- •What This Book Covers
- •How This Book Is Structured
- •What You Need to Use This Book
- •Conventions
- •Source Code
- •Errata
- •p2p.wrox.com
- •The Basics of C++
- •The Obligatory Hello, World
- •Namespaces
- •Variables
- •Operators
- •Types
- •Conditionals
- •Loops
- •Arrays
- •Functions
- •Those Are the Basics
- •Diving Deeper into C++
- •Pointers and Dynamic Memory
- •Strings in C++
- •References
- •Exceptions
- •The Many Uses of const
- •C++ as an Object-Oriented Language
- •Declaring a Class
- •Your First Useful C++ Program
- •An Employee Records System
- •The Employee Class
- •The Database Class
- •The User Interface
- •Evaluating the Program
- •What Is Programming Design?
- •The Importance of Programming Design
- •Two Rules for C++ Design
- •Abstraction
- •Reuse
- •Designing a Chess Program
- •Requirements
- •Design Steps
- •An Object-Oriented View of the World
- •Am I Thinking Procedurally?
- •The Object-Oriented Philosophy
- •Living in a World of Objects
- •Object Relationships
- •Abstraction
- •Reusing Code
- •A Note on Terminology
- •Deciding Whether or Not to Reuse Code
- •Strategies for Reusing Code
- •Bundling Third-Party Applications
- •Open-Source Libraries
- •The C++ Standard Library
- •Designing with Patterns and Techniques
- •Design Techniques
- •Design Patterns
- •The Reuse Philosophy
- •How to Design Reusable Code
- •Use Abstraction
- •Structure Your Code for Optimal Reuse
- •Design Usable Interfaces
- •Reconciling Generality and Ease of Use
- •The Need for Process
- •Software Life-Cycle Models
- •The Stagewise and Waterfall Models
- •The Spiral Method
- •The Rational Unified Process
- •Software-Engineering Methodologies
- •Extreme Programming (XP)
- •Software Triage
- •Be Open to New Ideas
- •Bring New Ideas to the Table
- •Thinking Ahead
- •Keeping It Clear
- •Elements of Good Style
- •Documenting Your Code
- •Reasons to Write Comments
- •Commenting Styles
- •Comments in This Book
- •Decomposition
- •Decomposition through Refactoring
- •Decomposition by Design
- •Decomposition in This Book
- •Naming
- •Choosing a Good Name
- •Naming Conventions
- •Using Language Features with Style
- •Use Constants
- •Take Advantage of const Variables
- •Use References Instead of Pointers
- •Use Custom Exceptions
- •Formatting
- •The Curly Brace Alignment Debate
- •Coming to Blows over Spaces and Parentheses
- •Spaces and Tabs
- •Stylistic Challenges
- •Introducing the Spreadsheet Example
- •Writing Classes
- •Class Definitions
- •Defining Methods
- •Using Objects
- •Object Life Cycles
- •Object Creation
- •Object Destruction
- •Assigning to Objects
- •Distinguishing Copying from Assignment
- •The Spreadsheet Class
- •Freeing Memory with Destructors
- •Handling Copying and Assignment
- •Different Kinds of Data Members
- •Static Data Members
- •Const Data Members
- •Reference Data Members
- •Const Reference Data Members
- •More about Methods
- •Static Methods
- •Const Methods
- •Method Overloading
- •Default Parameters
- •Inline Methods
- •Nested Classes
- •Friends
- •Operator Overloading
- •Implementing Addition
- •Overloading Arithmetic Operators
- •Overloading Comparison Operators
- •Building Types with Operator Overloading
- •Pointers to Methods and Members
- •Building Abstract Classes
- •Using Interface and Implementation Classes
- •Building Classes with Inheritance
- •Extending Classes
- •Overriding Methods
- •Inheritance for Reuse
- •The WeatherPrediction Class
- •Adding Functionality in a Subclass
- •Replacing Functionality in a Subclass
- •Respect Your Parents
- •Parent Constructors
- •Parent Destructors
- •Referring to Parent Data
- •Casting Up and Down
- •Inheritance for Polymorphism
- •Return of the Spreadsheet
- •Designing the Polymorphic Spreadsheet Cell
- •The Spreadsheet Cell Base Class
- •The Individual Subclasses
- •Leveraging Polymorphism
- •Future Considerations
- •Multiple Inheritance
- •Inheriting from Multiple Classes
- •Naming Collisions and Ambiguous Base Classes
- •Interesting and Obscure Inheritance Issues
- •Special Cases in Overriding Methods
- •Copy Constructors and the Equals Operator
- •The Truth about Virtual
- •Runtime Type Facilities
- •Non-Public Inheritance
- •Virtual Base Classes
- •Class Templates
- •Writing a Class Template
- •How the Compiler Processes Templates
- •Distributing Template Code between Files
- •Template Parameters
- •Method Templates
- •Template Class Specialization
- •Subclassing Template Classes
- •Inheritance versus Specialization
- •Function Templates
- •Function Template Specialization
- •Function Template Overloading
- •Friend Function Templates of Class Templates
- •Advanced Templates
- •More about Template Parameters
- •Template Class Partial Specialization
- •Emulating Function Partial Specialization with Overloading
- •Template Recursion
- •References
- •Reference Variables
- •Reference Data Members
- •Reference Parameters
- •Reference Return Values
- •Deciding between References and Pointers
- •Keyword Confusion
- •The const Keyword
- •The static Keyword
- •Order of Initialization of Nonlocal Variables
- •Types and Casts
- •typedefs
- •Casts
- •Scope Resolution
- •Header Files
- •C Utilities
- •Variable-Length Argument Lists
- •Preprocessor Macros
- •How to Picture Memory
- •Allocation and Deallocation
- •Arrays
- •Working with Pointers
- •Array-Pointer Duality
- •Arrays Are Pointers!
- •Not All Pointers Are Arrays!
- •Dynamic Strings
- •C-Style Strings
- •String Literals
- •The C++ string Class
- •Pointer Arithmetic
- •Custom Memory Management
- •Garbage Collection
- •Object Pools
- •Function Pointers
- •Underallocating Strings
- •Memory Leaks
- •Double-Deleting and Invalid Pointers
- •Accessing Out-of-Bounds Memory
- •Using Streams
- •What Is a Stream, Anyway?
- •Stream Sources and Destinations
- •Output with Streams
- •Input with Streams
- •Input and Output with Objects
- •String Streams
- •File Streams
- •Jumping around with seek() and tell()
- •Linking Streams Together
- •Bidirectional I/O
- •Internationalization
- •Wide Characters
- •Non-Western Character Sets
- •Locales and Facets
- •Errors and Exceptions
- •What Are Exceptions, Anyway?
- •Why Exceptions in C++ Are a Good Thing
- •Why Exceptions in C++ Are a Bad Thing
- •Our Recommendation
- •Exception Mechanics
- •Throwing and Catching Exceptions
- •Exception Types
- •Throwing and Catching Multiple Exceptions
- •Uncaught Exceptions
- •Throw Lists
- •Exceptions and Polymorphism
- •The Standard Exception Hierarchy
- •Catching Exceptions in a Class Hierarchy
- •Writing Your Own Exception Classes
- •Stack Unwinding and Cleanup
- •Catch, Cleanup, and Rethrow
- •Use Smart Pointers
- •Common Error-Handling Issues
- •Memory Allocation Errors
- •Errors in Constructors
- •Errors in Destructors
- •Putting It All Together
- •Why Overload Operators?
- •Limitations to Operator Overloading
- •Choices in Operator Overloading
- •Summary of Overloadable Operators
- •Overloading the Arithmetic Operators
- •Overloading Unary Minus and Unary Plus
- •Overloading Increment and Decrement
- •Overloading the Subscripting Operator
- •Providing Read-Only Access with operator[]
- •Non-Integral Array Indices
- •Overloading the Function Call Operator
- •Overloading the Dereferencing Operators
- •Implementing operator*
- •Implementing operator->
- •What in the World Is operator->* ?
- •Writing Conversion Operators
- •Ambiguity Problems with Conversion Operators
- •Conversions for Boolean Expressions
- •How new and delete Really Work
- •Overloading operator new and operator delete
- •Overloading operator new and operator delete with Extra Parameters
- •Two Approaches to Efficiency
- •Two Kinds of Programs
- •Is C++ an Inefficient Language?
- •Language-Level Efficiency
- •Handle Objects Efficiently
- •Use Inline Methods and Functions
- •Design-Level Efficiency
- •Cache as Much as Possible
- •Use Object Pools
- •Use Thread Pools
- •Profiling
- •Profiling Example with gprof
- •Cross-Platform Development
- •Architecture Issues
- •Implementation Issues
- •Platform-Specific Features
- •Cross-Language Development
- •Mixing C and C++
- •Shifting Paradigms
- •Linking with C Code
- •Mixing Java and C++ with JNI
- •Mixing C++ with Perl and Shell Scripts
- •Mixing C++ with Assembly Code
- •Quality Control
- •Whose Responsibility Is Testing?
- •The Life Cycle of a Bug
- •Bug-Tracking Tools
- •Unit Testing
- •Approaches to Unit Testing
- •The Unit Testing Process
- •Unit Testing in Action
- •Higher-Level Testing
- •Integration Tests
- •System Tests
- •Regression Tests
- •Tips for Successful Testing
- •The Fundamental Law of Debugging
- •Bug Taxonomies
- •Avoiding Bugs
- •Planning for Bugs
- •Error Logging
- •Debug Traces
- •Asserts
- •Debugging Techniques
- •Reproducing Bugs
- •Debugging Reproducible Bugs
- •Debugging Nonreproducible Bugs
- •Debugging Memory Problems
- •Debugging Multithreaded Programs
- •Debugging Example: Article Citations
- •Lessons from the ArticleCitations Example
- •Requirements on Elements
- •Exceptions and Error Checking
- •Iterators
- •Sequential Containers
- •Vector
- •The vector<bool> Specialization
- •deque
- •list
- •Container Adapters
- •queue
- •priority_queue
- •stack
- •Associative Containers
- •The pair Utility Class
- •multimap
- •multiset
- •Other Containers
- •Arrays as STL Containers
- •Strings as STL Containers
- •Streams as STL Containers
- •bitset
- •The find() and find_if() Algorithms
- •The accumulate() Algorithms
- •Function Objects
- •Arithmetic Function Objects
- •Comparison Function Objects
- •Logical Function Objects
- •Function Object Adapters
- •Writing Your Own Function Objects
- •Algorithm Details
- •Utility Algorithms
- •Nonmodifying Algorithms
- •Modifying Algorithms
- •Sorting Algorithms
- •Set Algorithms
- •The Voter Registration Audit Problem Statement
- •The auditVoterRolls() Function
- •The getDuplicates() Function
- •The RemoveNames Functor
- •The NameInList Functor
- •Testing the auditVoterRolls() Function
- •Allocators
- •Iterator Adapters
- •Reverse Iterators
- •Stream Iterators
- •Insert Iterators
- •Extending the STL
- •Why Extend the STL?
- •Writing an STL Algorithm
- •Writing an STL Container
- •The Appeal of Distributed Computing
- •Distribution for Scalability
- •Distribution for Reliability
- •Distribution for Centrality
- •Distributed Content
- •Distributed versus Networked
- •Distributed Objects
- •Serialization and Marshalling
- •Remote Procedure Calls
- •CORBA
- •Interface Definition Language
- •Implementing the Class
- •Using the Objects
- •A Crash Course in XML
- •XML as a Distributed Object Technology
- •Generating and Parsing XML in C++
- •XML Validation
- •Building a Distributed Object with XML
- •SOAP (Simple Object Access Protocol)
- •. . . Write a Class
- •. . . Subclass an Existing Class
- •. . . Throw and Catch Exceptions
- •. . . Read from a File
- •. . . Write to a File
- •. . . Write a Template Class
- •There Must Be a Better Way
- •Smart Pointers with Reference Counting
- •Double Dispatch
- •Mix-In Classes
- •Object-Oriented Frameworks
- •Working with Frameworks
- •The Model-View-Controller Paradigm
- •The Singleton Pattern
- •Example: A Logging Mechanism
- •Implementation of a Singleton
- •Using a Singleton
- •Example: A Car Factory Simulation
- •Implementation of a Factory
- •Using a Factory
- •Other Uses of Factories
- •The Proxy Pattern
- •Example: Hiding Network Connectivity Issues
- •Implementation of a Proxy
- •Using a Proxy
- •The Adapter Pattern
- •Example: Adapting an XML Library
- •Implementation of an Adapter
- •Using an Adapter
- •The Decorator Pattern
- •Example: Defining Styles in Web Pages
- •Implementation of a Decorator
- •Using a Decorator
- •The Chain of Responsibility Pattern
- •Example: Event Handling
- •Implementation of a Chain of Responsibility
- •Using a Chain of Responsibility
- •Example: Event Handling
- •Implementation of an Observer
- •Using an Observer
- •Chapter 1: A Crash Course in C++
- •Chapter 3: Designing with Objects
- •Chapter 4: Designing with Libraries and Patterns
- •Chapter 5: Designing for Reuse
- •Chapter 7: Coding with Style
- •Chapters 8 and 9: Classes and Objects
- •Chapter 11: Writing Generic Code with Templates
- •Chapter 14: Demystifying C++ I/O
- •Chapter 15: Handling Errors
- •Chapter 16: Overloading C++ Operators
- •Chapter 17: Writing Efficient C++
- •Chapter 19: Becoming Adept at Testing
- •Chapter 20: Conquering Debugging
- •Chapter 24: Exploring Distributed Objects
- •Chapter 26: Applying Design Patterns
- •Beginning C++
- •General C++
- •I/O Streams
- •The C++ Standard Library
- •C++ Templates
- •Integrating C++ and Other Languages
- •Algorithms and Data Structures
- •Open-Source Software
- •Software-Engineering Methodology
- •Programming Style
- •Computer Architecture
- •Efficiency
- •Testing
- •Debugging
- •Distributed Objects
- •CORBA
- •XML and SOAP
- •Design Patterns
- •Index
Chapter 24
Using the Objects
You’re now ready to use your distributed objects. Using the objects requires two steps. One piece of code must create an object and register it with the Object Request Broker (ORB) framework via the Portable Object Adapter. It must also provide a way for client code to lookup references to the object. One technique is to use a nameserver. This nameserver must be available from all the machines on which the distributed program runs. As a nameserver, it maps names to object references and tracks the actual physical location of all the distributed objects on the system. If you don’t have a nameserver available, you can use other ad hoc methods to register and lookup object references. Although the CORBA standard includes a nameserver, and omniORB supplies one, for simplicity our database example just writes the object reference key to a file.
The code that wants to use an object looks it up in the nameserver, or, in our case, the file, to retrieve a reference to it. When this client code calls a method on the reference, the request is sent to the ORB layer, which is either a layer in each process, or its own process on each node, depending on the implementation. At this point, there are two options. If the underlying object is in the same process as the caller, the method is executed locally as a normal C++ method call. However, if the underlying object is in a different process on the same machine, or on a remote machine, the ORB sends the method request over the network to the server process. All this work occurs under the surface: the code that makes the method call on the object reference doesn’t need to worry about whether the actual object is local or remote.
Figure 24-3 shows the basic CORBA architecture.
Machine 1 |
Machine 2 |
Client |
|
Object |
|
|
|
Lookup |
Implementation |
|
|
|
object |
|
|
|
|
reference |
Skeleton |
Call |
|
|
|
|||
|
Name |
|
Method |
|
|
|
Implementation |
||
Make |
server |
Portable Object |
||
|
||||
Method |
Stub |
Adapter |
|
|
Call |
|
|
|
|
|
ORB |
ORB |
|
|
|
Make Remote Procedure |
|
|
|
|
Cell using IIOP |
|
|
Figure 24-3
CORBA works for interprocess communication on the same machine as well as intermachine communication. You can use CORBA as a mechanism for “sharing” objects between processes on the same machine.
The rest of this section continues the database example by showing a sample implementation of a server and client. We don’t expect you to understand all the code details; we merely want to give you a sample
706
Exploring Distributed Objects
so you get a general feeling for CORBA programming and a taste of how powerful the technology can be. If you want to become an expert CORBA programmer, you should consult the references listed in Appendix B.
The Server Process
The server process must initialize the ORB, create a new DatabaseServer object, register the object, and save a key to its reference in a file for clients to find. This example assumes that clients will have access to the directory from which this server process is started, either through a network file system, or because they are running on the same node. The comments explain the steps taken. Note that you don’t use a special compiler to compile this code; you can use a standard C++ compiler, such as g++, as long as you link to the appropriate omniORB libraries.
#include “DatabaseServer.h” #include <iostream> #include <fstream>
using namespace std;
const char* objRefFile = “OBJ_REF_FILE.dat”;
int main(int argc, char** argv)
{
//Try to initialize the orb. CORBA::ORB_var orb;
try {
orb = CORBA::ORB_init(argc, argv); } catch(CORBA::SystemException&) {
cerr << “Unable to initialize the ORB\n”; exit(1);
}
//Obtain a reference to the “Portable Object Adapter” and downcast
//it to the appropriate type.
CORBA::Object_var obj = orb->resolve_initial_references(“RootPOA”);
PortableServer::POA_var poa = PortableServer::POA::_narrow(obj);
//Create the DatabaseServer object and register/activate it
//with the portable object adapter.
DatabaseServer* myDb = new DatabaseServer();
PortableServer::ObjectId_var dbid = poa->activate_object(myDb);
//Write a string version of the object reference to a
//file so clients can find us.
CORBA::Object_var dbobj = myDb->_this();
CORBA::String_var sior(orb->object_to_string(dbobj));
ofstream ostr(objRefFile); if (ostr.fail()) {
cerr << “Unable to open object reference file for writing.\n”; exit(1);
}
ostr << (char*)sior; ostr.close();
707
Chapter 24
// Tell the reference counter that we’re done with the object.
//Now only the POA has a reference to it. myDb->_remove_ref();
//Move the POA from holding to active state, so that it will process
//incoming requests.
PortableServer::POAManager_var pman = poa->the_POAManager(); pman->activate();
//Wait for incoming requests. orb->run();
//Shouldn’t return from the run call, but if we do, we need to clean up orb->destroy();
return (0);
}
The Client Process
The final step is to write a client process. Here is a basic client that reads the object reference key from a file, creates an object reference from the key, and calls two methods on the object reference. These calls are translated by the ORB layer into calls to the DatabaseServer object in the server process.
#include “database.hh” #include <iostream> #include <fstream> using namespace std;
const char* objRefFile = “OBJ_REF_FILE.dat”;
int main(int argc, char** argv)
{
//Try to initialize the orb. CORBA::ORB_var orb;
try {
orb = CORBA::ORB_init(argc, argv); } catch(CORBA::SystemException&) {
cerr << “Unable to initialize the ORB\n”; exit(1);
}
//Read the server object reference from the file. ifstream istr(objRefFile);
if (istr.fail()) {
cerr << “No object reference file!\n”; exit(1);
}
char objRef[1024]; istr.getline(objRef, 1024);
//Construct an object reference from the string. database_var dbref;
try {
CORBA::Object_var obj = orb->string_to_object(objRef);
708
Exploring Distributed Objects
dbref = database::_narrow(obj); if(CORBA::is_nil(dbref) ) {
cerr << “Can’t narrow reference to type database\n”; exit (1);
}
} catch(CORBA::SystemException&) {
cerr << “Unable to find the object reference\n”;
}
//Make calls on the object reference, which are translated to
//calls on the server object in the server process.
try {
dbref->addRecord(“key1”, “value1”);
const char* lookup = dbref->lookupRecord(“key1”); if (strcmp(lookup, “value1”) == 0) {
cout << “Success!\n”;
} else {
cout << “strings don’t match\n”;
}
} catch(CORBA::COMM_FAILURE&) { cerr << “Communication error\n”; exit(1);
} catch(CORBA::SystemException&) {
cerr << “Communication error (SystemException)\n”; exit(1);
}
// We’re done. orb->destroy();
return (0);
}
As you can see, the CORBA framework is certainly complicated and has a steep learning curve. However, it can be invaluable for industrial-strength distributed programming.
XML
Extensible Markup Language (XML) is a simple and general markup language. Fundamentally, XML can be used to represent just about anything. You could use XML as the file format for storing an MP3 music playlist or as the internal representation of a complex purchase order. Because XML is easy to work with and has cross-platform support, it has quickly become popular as a format for network communication, remote procedure calls, and distributed objects.
A Crash Course in XML
One of the greatest aspects of XML, and surely one of the reasons for its rapid adoption, is that it has a very friendly learning curve. Getting started with XML is easy. Within minutes, you’ll know the terminology and be able to read and write valid XML. From there, an XML developer can proceed down any
709
Chapter 24
number of more complex roads, such as XML transformations, or Simple Object Access Protocol (SOAP), a distributed object technology built upon XML.
What Is XML?
XML is merely a syntax for describing data. Outside of a specific application, XML data has no meaning. For example, you could write a home inventory program that produces a perfectly valid XML document describing all of your worldly possessions. If you gave that document to somebody else, they might be able to look at it and figure it out, but the XML-based home inventory program that they wrote wouldn’t necessarily be able to interpret it. The reason is that XML defines the structure of the document but not its meaning. Another application may represent the same information in a different structure.
XML is written in a plain text format, which makes it easy for human beings to grok. Even if you’ve never seen XML before, you can probably understand the following snippet of XML data:
<inventory>
<office>
<desk type=”wood”/> <computer type=”Macintosh”/> <chair type=”leather”/>
</office>
<kitchen>
<mixer type=”chrome”/> <stove type=”electric”/>
</kitchen>
</inventory>
In addition to readability, the text format also means that XML is easy to work with in software. You don’t need to learn a complicated framework or purchase an expensive toolset to parse text. Because even the most obscure operating systems understand plain text, it’s easy to send XML between systems without worrying about binary compatibility issues.
To be fair, textual representation has some downsides. Readability quickly becomes verbosity. An XML representation of data is usually larger than its equivalent binary representation. When the data set is large, the XML representation can grow to be enormous. Text also takes time to parse, unlike a binary format, which doesn’t require any parsing at all.
The other important characteristic about XML, implied by the indentation of the previous example, is that it is hierarchical. As you’ll see, XML is often parsed into a tree structure that you can walk through to process the data.
XML Structure and Terminology
XML documents begin with a document prolog, which specifies the character encoding and other metadata about the document. Many programmers omit the prolog, but some stricter XML parsers will fail to recognize the document as XML if the prolog is not found on the first line. The details of information that can be specified in the prolog are beyond the scope of this book. The following prolog is sufficient for most uses:
<?xml version=”1.0”?>
710
Exploring Distributed Objects
The document prolog is a special type of tag, a piece of syntax that XML recognizes as having some sort of meaning. If you have written HTML files, you’re already familiar with tags. The body of an XML document is made up of element tags. They are simply markers that identify the start and end of a logical piece of the structure. In XML, every starting element tag has a corresponding ending element tag. For example, the following line of XML uses the tag sentence to mark the start and end of a sentence element.
<sentence>Let’s go get some ice cream.</sentence>
In XML, the end tag is written as a slash followed by the name of the element. Element tags don’t always have to contain data as the previous example does. In XML, you can have an empty tag, which simply exists on its own. One way of doing this is to follow a start tag immediately with an end tag:
<empty></empty>
XML also provides a shorthand for empty element tags. If you end a tag with a slash, it serves as both the start tag and the end of the element:
<empty />
The topmost element, which contains all other elements in the document, is known as the root element.
In addition to its name, an element tag can contain key/value pairs called attributes. There are no set-in- stone rules about what can be written as an attribute (remember: XML is just a syntax) but in general, attributes provide metainformation about the element. For example, the sentence element could have an attribute that gives the speaker of the sentence:
<sentence speaker=”Marni”>Let’s go get some ice cream.</sentence>
Elements can have multiple attributes, though they must have unique keys:
<sentence speaker=”Marni” tone=”pleading”>Let’s go get some ice cream.</sentence>
When you see an XML element whose name has a colon in it, such as <a:sentence>, the string prior to the colon, is its namespace. Just like namespaces in C++, namespaces in XML allow you to segment the use of names.
In the previous examples, the content of an element was either empty or textual data, commonly referred to as a text node. In XML, elements can also contain other elements, which gives XML its hierarchical structure. In the following example, the dialogue element is made up of two sentence elements. Note that the indentation exists only for readability — XML ignores white space between tags.
<dialogue>
<sentence speaker=”Marni”>Let’s go get some ice cream.</sentence>
<sentence speaker=”Scott”>After I’m done writing this C++ book.</sentence> </dialogue>
Those are the basics! Elements, attributes, and text nodes are the building blocks of XML. For more advanced syntax, such as special character escaping, consult one of the XML reference books listed in Appendix B.
711