Bounding Object Instantiations, Part 2

Similar documents
Programming in C++ Prof. Partha Pratim Das Department of Computer Science and Engineering Indian Institute of Technology, Kharagpur

Programming in C++ Prof. Partha Pratim Das Department of Computer Science and Engineering Indian Institute of Technology, Kharagpur

Ch. 11: References & the Copy-Constructor. - continued -

3.Constructors and Destructors. Develop cpp program to implement constructor and destructor.

QUIZ. What is wrong with this code that uses default arguments?

Chapter 1 Getting Started

static CS106L Spring 2009 Handout #21 May 12, 2009 Introduction

Critique this Code. Hint: It will crash badly! (Next slide please ) 2011 Fawzi Emad, Computer Science Department, UMCP

G52CPP C++ Programming Lecture 20

QUIZ Friends class Y;

Proposal to Simplify pair (rev 4)

Fast Introduction to Object Oriented Programming and C++

CSE 374 Programming Concepts & Tools. Hal Perkins Spring 2010

Instantiation of Template class

Item 4: Extensible Templates: Via Inheritance or Traits?

See the CS 2704 notes on C++ Class Basics for more details and examples. Data Structures & OO Development I

Exception Namespaces C Interoperability Templates. More C++ David Chisnall. March 17, 2011

Class 15. Object-Oriented Development from Structs to Classes. Laura Marik Spring 2012 C++ Course Notes (Provided by Jason Minski)

Lesson 10A OOP Fundamentals. By John B. Owen All rights reserved 2011, revised 2014

Pointers, Arrays and Parameters

Compuer Science 62 Assignment 10

G52CPP C++ Programming Lecture 7. Dr Jason Atkin

Financial computing with C++

Chapter 16. Templates. Copyright 2010 Pearson Addison-Wesley. All rights reserved

1 Dynamic Memory continued: Memory Leaks

Scope. Chapter Ten Modern Programming Languages 1

Introduction to C++ Introduction to C++ Dr Alex Martin 2013 Slide 1

C Pointers 2013 Author Riko H i

CSE 303: Concepts and Tools for Software Development

Programming in C ++ Prof. Partha Pratim Das Department of Computer Science and Engineering Indian Institute of Technology, Kharagpur

G52CPP C++ Programming Lecture 13

D Programming Language

Project 3 Due October 21, 2015, 11:59:59pm

class Polynomial { public: Polynomial(const string& N = "no name", const vector<int>& C = vector<int>());... };

Ch. 12: Operator Overloading

CS201 Some Important Definitions

Let return Be Direct and explicit

Software Design and Analysis for Engineers

American National Standards Institute Reply to: Josee Lajoie

STUDY NOTES UNIT 1 - INTRODUCTION TO OBJECT ORIENTED PROGRAMMING

QUIZ on Ch.5. Why is it sometimes not a good idea to place the private part of the interface in a header file?

QUIZ How do we implement run-time constants and. compile-time constants inside classes?

C++ Programming Lecture 7 Software Engineering Group

Lecture 13: more class, C++ memory management

Object-Oriented Principles and Practice / C++

class objects instances Fields Constructors Methods static

EL2310 Scientific Programming

Lecture 19 CSE August You taught me Language, and my profit on t is I know how to curse. William Shakspere, The Tempest, I, ii.

Programming in C++ Prof. Partha Pratim Das Department of Computer Science and Engineering Indian Institute of Technology, Kharagpur

CSCI-1200 Data Structures Spring 2018 Lecture 10 Vector Iterators & Linked Lists

Lecture 10: building large projects, beginning C++, C++ and structs

Chapter 4 Defining Classes I

Chapter 13: Copy Control. Overview. Overview. Overview

CS 536 Introduction to Programming Languages and Compilers Charles N. Fischer Lecture 11

Programming, numerics and optimization

2. List Implementations (a) Class Templates (b) Contiguous (c) Simply Linked (d) Simply Linked with Position Pointer (e) Doubly Linked

Object Oriented Programming. Assistant Lecture Omar Al Khayat 2 nd Year

Classes, interfaces, & documentation. Review of basic building blocks

OBJECT ORIENTED PROGRAMMING

Data Structures using OOP C++ Lecture 3

CSCI-1200 Data Structures Fall 2017 Lecture 10 Vector Iterators & Linked Lists

Contents A Little C++

Cpt S 122 Data Structures. Introduction to C++ Part II

CSE 374 Programming Concepts & Tools. Hal Perkins Fall 2015 Lecture 19 Introduction to C++

Increases Program Structure which results in greater reliability. Polymorphism

377 Student Guide to C++

public class Foo { private int var; public int Method1() { // var accessible anywhere here } public int MethodN() {

Animals Due Part 1: 19 April Part 2: 26 April

The name of our class will be Yo. Type that in where it says Class Name. Don t hit the OK button yet.

G52CPP C++ Programming Lecture 9

Introduction to C++ Part II. Søren Debois. Department of Theoretical Computer Science IT University of Copenhagen. September 12th, 2005

After a lecture on cosmology and the structure of the solar system, William James was accosted by a little old lady.

Chapter 18 Vectors and Arrays [and more on pointers (nmm) ] Bjarne Stroustrup

1. Describe History of C++? 2. What is Dev. C++? 3. Why Use Dev. C++ instead of C++ DOS IDE?

APPENDIX B. Fortran Hints

15-498: Distributed Systems Project #1: Design and Implementation of a RMI Facility for Java

CMSC162 Intro to Algorithmic Design II Blaheta. Lab March 2019

Static initializers in C++

CS 370 The Pseudocode Programming Process D R. M I C H A E L J. R E A L E F A L L

Programming Exercise 14: Inheritance and Polymorphism

Pointers and References

1 Inheritance (8 minutes, 9 points)

Type Checking in COOL (II) Lecture 10

Lecture Topics. Administrivia

C++ for Java Programmers

CS103 Handout 29 Winter 2018 February 9, 2018 Inductive Proofwriting Checklist

Java Threads and intrinsic locks

CPSC 427a: Object-Oriented Programming

CSCI-1200 Data Structures Spring 2016 Lecture 6 Pointers & Dynamic Memory

Chapter 1: Object-Oriented Programming Using C++

Programming Languages: Encapsulation

Structured Data. CIS 15 : Spring 2007

Function Call Stack and Activation Records

An Introduction to C++

OBJECT-ORIENTED PROGRAMMING CONCEPTS-CLASSES III

Variables, Memory and Pointers

Principle of Complier Design Prof. Y. N. Srikant Department of Computer Science and Automation Indian Institute of Science, Bangalore

C++ Inheritance and Encapsulation

Assignment 1: grid. Due November 20, 11:59 PM Introduction

CSCI-1200 Data Structures Fall 2011 Lecture 24 Garbage Collection & Smart Pointers

Transcription:

This is a pre-publication draft of the column I wrote for the June 1995 issue of the C++ Report. Pre-publication means this is what I sent to the Report, but it may not be exactly the same as what appeared in print, because the Report and I often make small changes after I submit the final draft for a column. Comments? Feel free to send me mail: smeyers@aristeia.com. Bounding Object Instantiations, Part 2 In my last column (March-April 1995), I introduced the problem of bounding the number of instantiations of a class, and I considered how to allow zero and one instances. I also discussed how there s ambiguity surrounding what it means to instantiate an object, because objects can exist in three different contexts: on their own, as the base class part of a more derived object, and as a member of an enclosing object. In this column I ll continue the discussion of bounding object instantiations, and I ll bring it to a close. We now know how to design a class that allows but a single instantiation, we know that keeping track of the number of objects of a particular class is complicated by the fact that object constructors are called in three different contexts, and we know that we can eliminate the confusion surrounding object counts by making constructors private. It is time, therefore, to revisit our original goal, that of limiting the number of objects of a particular type. Before doing that, however, it is worthwhile to make one final observation. Our use of the theprinter function to encapsulate access to a single object certainly limits the number of Printer objects to one, but it also limits us to a single Printer object for each program run. As a result, it s not possible to write code like this: create Printer object p1; use p1; destroy p1; create Printer object p2; use p2; destroy p2; This design never instantiates more than a single Printer object at a time, but it does use different Printer objects in different parts of the program. It somehow seems unreasonable that this isn t allowed. After all, at no point do we violate the constraint that only

one printer may exist. Isn t there a way to make this legal while still honoring the restriction that we can t have more than a single printer? There is. All we have to do is combine the object-counting code we used earlier with the pseudo-constructors we saw near the end of my last column: class Printer static unsigned short numobjects = 0; Printer(); Printer(const Printer& rhs); class TooManyObjects; // pseudo-constructors static Printer * makeprinter(); static Printer * makeprinter(const Printer& rhs); ; ~Printer(); void submitjob(const PrintJob& job); void reset(); void performselftest(); // Obligatory definitions of class static unsigned short Printer::numObjects; Printer::Printer() if (numobjects >= 1) throw TooManyObjects(); proceed with normal object construction here; Printer::Printer(const Printer& rhs) if (numobjects >= 1) throw TooManyObjects(); proceed with normal object construction here; Printer * Printer::makePrinter() return new Printer;

Printer * Printer::makePrinter( const Printer& rhs) return new Printer(rhs); If the notion of throwing an exception when too many objects are requested strikes you as unreasonably harsh, you could have the pseudo-constructors return 0 instead. Clients would then have to check for the null pointer before doing anything with it, of course. Clients use this Printer class just like they would any other class, except they must call pseudo-constructor functions instead of regular constructors and they must delete the objects they create: Printer p1; // error! default ctor is // private Printer *p2 = Printer::makePrinter(); // fine, indirectly calls // default ctor Printer p3 = *p2; // error! copy ctor is // private Printer *p4 = Printer::makePrinter(p3); // fine, indirectly calls // copy ctor (but throws // an exception because // *p2 already exists) p2->performselftest(); // all other functions p2->reset(); // are called as usual delete p2; // avoid memory leak This technique is trivially generalizable to any number of objects. All we have to do is replace the hard-wired constant 1 in the code above with a class-specific value. For example, the following revised implementation of our Printer class allows up to 10 Printer objects to exist at any given time: class Printer static unsigned short numobjects = 0; static unsigned short maxobjects = 10; ; // as above // Obligatory definitions of class statics unsigned short Printer::numObjects; unsigned short Printer::maxObjects; Printer::Printer() if (numobjects >= maxobjects)

throw TooManyObjects(); Printer::Printer(const Printer& rhs) if (numobjects >= maxobjects) throw TooManyObjects(); This approach works like the proverbial charm, but there is one aspect of it that continues to nag. If we had a lot of classes like Printer whose instantiations needed to be limited, we d have to write this same code over and over, once per class. That would be mind-numbingly dull. Given a fancy-pants language like C++, it somehow seems that we should be able to automate the process. Isn t there a way to encapsulate the notion of counting instances and bundle it into a class? Surely we can come up with a base class for counting object instances and have classes like Printer inherit from that, but it turns out we can do even better. We can actually come up with a way to encapsulate the whole counting kit and kaboodle, by which I mean not only the functions to manipulate the instance count, but also the instance count itself. The counter in the Printer class is the static variable numobjects, so we need to move that variable into an instance-counting class. However, we also need to make sure that each class for which we re counting instances has a separate counter. Use of a counting class template lets us automatically generate the appropriate number of counters with the greatest of ease, because we can make the counter a static member of the classes generated from the template: template<class BeingCounted> class Counted static unsigned short numobjects = 0; static unsigned short maxobjects; protected: Counted(); Counted(const Counted& rhs); ~Counted() --numobjects; class TooManyObjects; // for throwing exceptions static int objectcount() return numobjects; ;

template<class BeingCounted> Counted::Counted() if (numobjects >= maxobjects) throw TooManyObjects(); template<class BeingCounted> Counted::Counted(const Counted&) if (numobjects >= maxobjects) throw TooManyObjects(); The classes generated from this template are designed only to be used as base classes, hence the protected constructors and destructor. You may notice that the objectcount function has a return type of int, but it actually returns a value that is unsigned. This seems like a loss-of-precision error just waiting to happen, and some compilers get so nervous about it they issue a warning. Unfortunately, returning the unsigned short directly isn t much better (most callers will just treat it as an int anyway), so we ll just hope an int is bigger than a short or we never have more objects than an int can represent. This is hardly iron-clad software engineering, but the alternative eschewing unsigned types entirely isn t that appealing, either. We can now modify the Printer class to use the Counted template: class Printer: private Counted<Printer> Printer(); Printer(const Printer& rhs); // pseudo-constructors static Printer * makeprinter(); static Printer * makeprinter( const Printer& rhs); ~Printer(); void submitjob(const PrintJob& job); void reset(); void performselftest(); using Counted<Printer>::objectCount; ; Now, the fact that Printer uses the Counted template to keep track of how many Printer objects exist is, frankly, nobody s business but the author of Printer s. Such implementation details are best kept private, and that s why private inheritance is used here. Quite properly, most of what Counted does is hidden from Printer s clients, but those clients might reasonably want to find

out how many Printer objects exist. The Counted template provides the objectcount function to provide this information, but that function becomes private in Printer due to our use of private inheritance. To restore the public accessibility of that function, we employ a using declaration: class Printer: private Counted<Printer> using Counted<Printer>::objectCount; // make this function // public for clients // of Printer ; This is perfectly legitimate, according to the emerging language standard, but if your compilers don t yet support namespaces, they are unlikely to allow it. If they don t, you can use the older accessdeclaration syntax: class Printer: private Counted<Printer> Counted<Printer>::objectCount; // make objectcount // public in Printer ; This more traditional syntax has the same meaning as the using declaration, but it s deprecated, meaning it will eventually be removed from the language. Needless to say, you should avoid the use of deprecated features whenever you can, but don t lose too much sleep if you find that your compilers force you into a little deprecation now and then. It will be some time years, probably before such deprecated features are actually made illegal. When Printer inherits from Counted<Printer>, it can completely forget about counting objects. The class can be written as if somebody else were doing the counting for it, because somebody else (Counted<Printer>) is. For example a Printer constructor now looks like this: Printer::Printer() proceed with normal object construction; What s interesting here is not what you see, it s what you don t. No checking of the number of objects to see if the limit is about to be exceeded, no incrementing of the number of objects in existence once the constructor is done. All that is now handled by the Counted<Printer> constructors, and because Counted<Printer> is a base class of Printer, we know that a Counted<Printer> constructor will always be called before a Printer constructor is. If too many objects are created, a

Counted<Printer> constructor will throw an exception, and the Printer constructor won t even be invoked. Nifty, huh? Nifty or not, however, there s one loose end that demands to be tied, and that s the mandatory definitions of the statics inside Counted. It s easy enough to take care of numobjects we just put this in Counted s implementation file: template<class BeingCounted> unsigned short Counted::numObjects; The situation with maxobjects is a bit trickier. To what value should we initialize this variable? If we want to allow up to 10 printers, we should initialize Counter<Printer>::maxObjects to 10. If, on the other hand, we want to allow up to 16 file descriptor objects, we should initialize Counted<FileDescriptor>::maxObjects to 16. What to do? We take the easy way out: we do nothing. We provide no initialization at all for maxobjects. Instead, we require that clients of the class provide the appropriate initialization. The author of Printer, then, must add this to an implementation file: unsigned short Counted<Printer>::maxObjects = 10; Similarly, the author of FileDescriptor must add this: unsigned short Counted<FileDescriptor>::maxObjects = 16; What will happen, you might wonder, if these authors forget to provide a suitable definition for maxobjects? Simple: they ll get an error at link time, because maxobjects will be undefined. Provided we ve adequately documented this requirement for clients of Counted, they can then mutter Duh to themselves and go back and add the requisite initialization. Acknowledgment My design of a template class for counting objects is based on a posting to comp.lang.c++ by Jamshid Afshar.