Developing a large-scale software system in C++ requires more than just a sound understanding of the logical design issues covered in most books on C++ programming. To be successful, you will also need a grasp of physical design concepts that, while closely tied to the technical aspects of development, include a dimension with which even expert software developers may have little or no experience. This is the definitive book for all C++ software professionals involved in large development efforts such as databases, operating systems, compilers, and frameworks. It is the first C++ book that actually demonstrates how to design large systems, and one of the few books on object-oriented design specifically geared to practical aspects of the C++ programming language. In this book, Lakos explains the process of decomposing large systems into physical (not inheritance) hierarchies of smaller, more manageable components. Such systems with their acyclic physical dependencies are fundamentally easier and more economical to maintain, test, and reuse than tightly interdependent systems. In addition to explaining the motivation for following good physical as well as logical design practices, Lakos provides you with a catalog of specific techniques designed to eliminate cyclic, compile-time, and link-time (physical) dependencies. He then extends these concepts from large to very large systems. The book concludes with a comprehensive top-down approach to the logical design of individual components. Appendices include a valuable design pattern "Protocol Hierarchy" designed to avoid fat interfaces while minimizing physical dependencies; the details of implementing an ANSI C compatibleC++ procedural interface; and a complete specification for a suite of UNIX-like tools to extract and analyze physical dependencies. Practical design rules, guidelines, and principles are also collected in an appendix and indexed for quick reference. Booknews The topic is of prime importance to software professionals involved in large development efforts such as databases, operating systems, compilers, and frameworks. This volume explains the process of decomposing large systems into physical (not inheritance) hierarchies of small, manageable components. Concepts and techniques are illustrated with "war stories" from the development firm, Mentor Graphics, as well as with a large-scale example comprising some 12,000 lines of code. Annotation c. Book News, Inc., Portland, OR (booknews.com) Large-Scale C++ Software Design Figure List Preface Chapter 0: Introduction O.1 From C to C++ 0.2 Using C++ to Develop Large Projects 0.2.1 Cyclic Dependencies 0.2.2 Excessive Link-Time Dependencies 0.2.3 Excessive Compile-Time Dependencies 0.2.4 The Global Name Space 0.2.5 Logical vs. Physical Design 0.3 REUSE 0.4 Quality 0.4.1 Quality Assurance 0.4.2 Quality Ensurance 0.5 Software Development Tools 0.6 Summary PART I: BASICS Chapter 1: Preliminaries 1.1 Multi-File C++ Programs 1.1.1 Declaration versus Definition 1.1.2 Internal versus External Linkage 1.1.3 Header (.h) Files 1.1.4 Implementation (.c) Files 1.2 typedef Declarations 1.3 Assert Statements 1.4 A Few Matters of Style 1.4.1 Identifier Names 1.4.1.1 Type Names 1.4.1.2 Multi-Word Identifier Names 1.4.1.3 Data Member Names 1.4.2 Class Member Layout 1.5 Iterators 1.6 Logical Design Notation 1.6.1 The IsA Relation 1.6.2 The Uses-In-The-Interface Relation 1.6.3. The Uses-In-The-Implementation Relation 1.6.3.1 Uses 1.6.3.2 HasA and HoldsA 1.6.3.3 WasA 1.7 Inheritance versus Layering 1.8 Minimality 1.9 Summary Chapter 2: Ground Rules 2.1 Overview 2.2 Member Data Access 2.3 The Global Name Space 2.3.1 Global Data 2.3.2 Free Functions 2.3.3, Enumerations, Typedefs, and Constant Data 2.3.4 Preprocessor Macros 2.3.5 Names in Header Files 2.4 Include Guards 2.5 Redundant Include Guards 2.6 Documentation 2.7 Identifier-Naming Conventions 2.8 Summary PART II: PHYSICAL DESIGN CONCEPTS Chapter 3: Components 3.1 Components versus Classes 3.2 Physical Design Rules 3.3 The DependsOn Relation 3.4 Implied Dependency 3.5 Extracting Actual Dependencies 3.6 Friendship 3.6.1 Long-Distance Friendship and Implied Dependency 3.6.2 Friendship and Fraud 3.7 Summary Chapter 4: Physical Hierarchy 4.1 A Metaphor for Software Testing 4.2 A Complex Subsystem 4.3 The Difficulty in Testing “Good” Interfaces 4.4 Design for Testability 4.5 Testing in Isolation 4.6 Acyclic Physical Dependencies 4.7 Level Numbers 4.7.1 The Origin of Level Numbers 4.7.2 Using Level Numbers in Software 4.8 Hierarchical and Incremental Testing 4.9 Testing a Complex Subsystem 4.10 Testability versus Testing 4.11 Cyclic Physical Dependencies 4.12 Cumulative Component Dependency 4.13 Physical Design Quality 4.14 Summary Chapter 5: Levelization 5.1 Some Causes of Cyclic Physical Dependencies 5.1.1 Enhancement 5.1.2 Convenience 5.1.3 Intrinsic Interdependency 5.2 Escalation 5.3 DeMotion 5.4 Opaque Pointers 5.5 Dumb Data 5.6 Redundancy 5.7 Callbacks 5.8 Manager Class 5.9 Factoring 5.10 Escalating Encapsulation 5.11 Summary Chapter 6: Insulation 6.1 From Encapsulation to Insulation 6.1.1 The Cost of Compile-Time Coupling 6.2 C++ and Compile-Time Coupling 6.2.1 Inheritance (IsA) and Compile-Time Coupling 6.2.2 Layering (HasA/HoldsA) and Compile-Time Coupling 6.2.3 Inline Functions and Compile-Time Coupling 6.2.4 Private Members and Compile-Time Coupling 6.2.5 Protected Members and Compile-Time Coupling 6.2.6 Compiler-Generated Member Functions and Compile-Time Coupling 6.2.7 Include Directives and Compile-Time Coupling 6.2.8 Default Arguments and Compile-Time Coupling 6.2.9 Enumerations and Compile-Time Coupling 6.3 Partial Insulation Techniques 6.3.1 Removing Private Inheritance 6.3.2 Removing Embedded Data Members 6.3.3 Removing Private Member Functions 6.3.4 Removing Protected Members 6.3.5 Removing Private Member Data 6.3.6 Removing Compiler-Generated Functions 6.3.7 Removing Include Directives 6.3.8 Removing Default Argument 6.3.9 Removing Enumerations 6.4 Total Insulation Techniques 6.4.1 The Protocol Class 6.4.2 The Fully Insulating Concrete Class 6.4.3 The Insulating Wrapper 6.4.3.1 Single-Component Wrappers 6.4.3.2 Multi-Component Wrappers 6.5 The Procedural Interface 6.5.1 The Procedural Interface Architecture 6.5.2 Creating and Destroying Opaque Objects 6.5.3 Handles 6.5.4 Accessing and Manipulating Opaque Objects 6.5.5 Inheritance and Opaque Objects 6.6 To Insulate or Not to Insulate 6.6.1 The Cost of Insulation 6.6.2 When Not to Insulate 6.6.3 How to Insulate 6.6.4 How Much to Insulate 6.7 Summary Chapter 7: Packages 7.1 From Components to Packages 7.2 Registered Package Prefixes 7.2.1 The Need for Prefixes 7.2.2 Namespaces 7.2.3 Preserving Prefix Integrity 7.3 Package Levelization 7.3.1 The Importance of Levelizing Packages 7.3.2 Package Levelization Techinques 7.3.3 Partitioning a System 7.3.4 Multi-Site Development 7.4 Package Insulation 7.5 Package Groups 7.6 The Release Process 7.6.1 The Release Structure 7.6.2 Patches 7.7 The main Program 7.8 Start-Up 7.8.1 Initialization Strategies 7.8.1.1 The Wake-Up Initialized Technique 7.8.1.2 The Explicit init Function Technique 7.8.1.3 The Nifty Counter Technique 7.8.1.4 The Check-Every-Time Technique 7.8.2 Clean-Up 7.8.3 Review 7.9 Summary PART III: LOGICAL DESIGN ISSUES Chapter 8: Architecting a Component 8.1 Abstractions and Components 8.2 Component Interface Design 8.3 Degrees of Encapsulation 8.4 Auxiliary Implementation Classes 8.5 Summary Chapter 9: Designing a Function 9.1 Function Interface Specification 9.1.1 Operator or Non-Operator Function 9.1.2 Free or Member Operator 9.1.3 Virtual or Non-Virtual Function 9.1.4 Pure or Non-Pure Virtual Member Function 9.1.5 Static or Non-Static Member Function 9.1.6 const Member or Non-const Member Function 9.1.7 Public, Protected, or Private Member Function 9.1.8 Return by Value, Reference, Or Pointer 9.1.9 Return const Or Non-const 9.1.10 Argument Optional or Required 9.1.11 Pass Argument by Value, Reference, or Pointer 9.1.12 Pass Argument as const or Non-const 9.1.13 Friend or Non-Friend Function 9.1.14 Inline or Non-Inline Function 9.2 Fundamental Types Used in the Interface 9.2.1 Using short in the Interface 9.2.2 Using unsigned in the Interface 9.2.3 Using long in the Interface 9.2.4 Using float, double, and long double in the Interface 9.3 Special-Case Functions 9.3.1 Conversion Operators 9.3.2 Compiler-Generated Value Semantics 9.3.3 The Destructor Chapter 10: Implementing an Object 10.1 Member Data 10.1.1 Natural Alignment 10.1.2 Fundamental Types Used in the Implementation 10.1.3 Using typedef in the Implementation 10.2 Function Definitions 10.2.1 Assert Yourself 10.2.2 Avoid Special Casing 10.2.3 Factor Instead of Duplicate 10.2.4 Don’t Be Too Clever 10.3 Memory Management 10.3.1 Logical versus Physical State Values 10.3.2 Physical Parameters 10.3.3 Memory 10.3.4 Class-Specific Memory Management 10.3.4.1 Adding Custom Memory Management 10.3.4.2 Hogging Memory 10.3.5 Object-Specific Memory Management 10.4 Using C++ Templates in Large Projects 10.4.1 Compiler Implementations 10.4.2 Managing Memory in Templates 10.4.3 Patterns versus Templates 10.5 Summary Appendix A The Protocol Hierarchy Intent Also Known As Motivation Applicability Structure Participants Collaborations Consequences Implementation Sample Code Known Uses Related Patterns Appendix B Implementing an ANSI C-Compatible C++ Interface B.1 Memory Allocation Error Detection B.2 Providing a Main Procedure (ANSI C Only) Appendix C A Dependency Extractor/Analyzer Package C.1 Using adep, cdep, and 1dep C.2 Command-Line Documentation C.3 Idep Package Architecture C.4 Source Code Appendix D Quick Reference D.1 Definitions D.2 Major Design Rules D.3 Minor Design Rules D.4 Guidelines D.5 Principles Bibliography Index Geared to practical aspects of the C++ programming language, this book demonstrates how to design large systems. The author explains the process of decomposing large systems into physical hierarchies of smaller, more manageable components. This book concludes with a top-down approach to the logical design of individual components.